迭代器和生成器原理
《Python3高级核心技术97讲,bobby》学习笔记,第八章:迭代器和生成器原理。
411阅读 · 2020-6-14 23:17发布
8.1 迭代协议
- 迭代器是访问集合内元素的一种方式,一般用来遍历数据。
- 迭代器和以下标的访问方式不一样,迭代器是不能返回的,迭代器提供了一种惰性访问数据的方式(访问数据的时候才计算)。
- __iter__魔法方法就是迭代协议。
- 可迭代对象(Iterable)只需要实现__iter__魔法方法。
- 迭代器(Iterator)继承了Iterable,实现__next__魔法方法和__iter__方法。
抽象基类代码:
class Iterable(Protocol[_T_co]): @abstractmethod def __iter__(self) -> Iterator[_T_co]: ... class Iterator(Iterable[_T_co], Protocol[_T_co]): @abstractmethod def __next__(self) -> _T_co: ... def __iter__(self) -> Iterator[_T_co]: ...
8.2 什么是迭代器和可迭代对象
- 使用iter()方法时,如果目标对象没有实现__iter__魔法函数,则会使用__getitem__返回一个iterator对象。
- 迭代器会在内部维护一个index用于计数。
- 迭代的逻辑交给迭代器完成,是一种迭代器设计模式(使可迭代对象无需维护迭代器中的内置变量)。
可迭代对象实现迭代器代码示例:
from collections.abc import Iterator class Company(object): def __init__(self,employee_list): self.employee = employee_list def __iter__(self): return MyIterator(self.employee) class MyIterator(Iterator): def __init__(self, employee_list): self.iter_list = employee_list self.index = 0 def __next__(self): try: word = self.iter_list[self.index] except IndexError: raise StopIteration self.index += 1 return word if __name__ == "__main__": company = Company(["tom", "jack", "marry"]) for item in company: print(item)
8.3 生成器函数的使用
- 函数中包含yield,就是生成器函数。
- yield返回的是生成器对象generator。
- 生成器对象在python编译字节码的时候就产生的。
- 执行时会从上一次的yield处继续执行。
- 生成器函数可以return一个值(早期版本python不行)。
使用生成器函数实现斐波拉契:
def gen_fib(index): n,a,b = 0,0,1 while n<index: yield b a,b = b,a+b n +=1 for data in gen_fib(10): print(data)
8.4 生成器的原理
函数运行原理
def foo():
bar()
def bar():
pass
- python解释器由C编写,它会使用C语言函数PyEval_EvalFramEx执行foo函数。
- 首先会创建一个栈帧(stack frame),该栈帧也是一个对象。
- 此时foo函数也是字节码对象。
- 可以通过内置函数dis的dis()方法查看对象字节码。
import dis print(dis.dis(foo))
- 在栈帧的上下文中,运行字节码,字节码全局唯一。
- foo函数调用子函数bar时,会再次创建一个栈帧,将函数的控制权交给栈帧对象。
- 在新创建的栈帧中运行bar字节码。
- 此时拥有两个栈帧,所有的栈帧都分配在堆内存中(堆的特点是不释放则一直存在在内存中)。
- 所以说明栈帧独立于调用者存在(原调用者后面不存在也可以)。
生成器函数利用了python函数调用过程(栈帧都分配在堆内存上的特性)
- python解释器在遇到yield关键字,就会标记函数为生成器函数。
- 当调用生成器函数时会返回生成器对象(实际是对frame做了封装)。
生成器对象的gi_frame.f_lasti存放字节码位置,gi_frame.f_locals存放值(因此生成器可以找到上一次执行的位置)。
生成器分配在堆内存中,只要拿到生成器对象(栈帧对象),就可以在任何地方控制生成器(也是实现协程的基础)。
8.5 使用生成器的UserList
- collcetions的UserList是用python实现的list。(list是C语言写的)
UserList继承于Sequence,Sequence中实现了iter魔法函数。示例:
class Sequence(Reversible, Collection): """All the operations on a read-only sequence. Concrete subclasses must override __new__ or __init__, __getitem__, and __len__. """ __slots__ = () @abstractmethod def __getitem__(self, index): raise IndexError def __iter__(self): i = 0 try: while True: v = self[i] yield v i += 1 except IndexError: return ...省略...
8.6 使用生成器读取大文件
文件500g,只有一行,使用{|}分割。示例代码:
def myreadlines(f,newline): buf = "" while True: while newline in buf: pos = buf.index(newline) yield buf[:pos] buf = buf[pos + len(newline):] chunk = f.read(4096) if not chunk: yield buf break buf += chunk with open('input.txt') as f: for line in myreadlines(f,"{|}"): print(line)