一、Python的迭代协议
迭代器是访问集合内元素的一种方式。它只能依次访问集合内元素。其特点是惰性执行。
collection.abc的迭代协议提供了两个概念:可迭代对象和迭代器。可迭代对象:必须具有__item__特殊方法;迭代器:必须具有__next__方法。
class Iterable(metaclass=ABCMeta): __slots__ = () @abstractmethod def __iter__(self): while False: yield None @classmethod def __subclasshook__(cls, C): if cls is Iterable: return _check_methods(C, "__iter__") return NotImplemented class Iterator(Iterable): __slots__ = () @abstractmethod def __next__(self): 'Return the next item from the iterator. When exhausted, raise StopIteration' raise StopIteration def __iter__(self): return self # 重载了Iterable的__iter__ @classmethod def __subclasshook__(cls, C): if cls is Iterator: return _check_methods(C, '__iter__', '__next__') return NotImplemented Iterator.register(bytes_iterator) Iterator.register(bytearray_iterator) #Iterator.register(callable_iterator) Iterator.register(dict_keyiterator) Iterator.register(dict_valueiterator) Iterator.register(dict_itemiterator) Iterator.register(list_iterator) Iterator.register(list_reverseiterator) Iterator.register(range_iterator) Iterator.register(longrange_iterator) Iterator.register(set_iterator) Iterator.register(str_iterator) Iterator.register(tuple_iterator) Iterator.register(zip_iterator)
从上面可以看到,字节、字典、列表、集合、元组、字符串和range、zip都被注册到了迭代器元类中,成为可迭代对象。实际上,open文件句柄也是一个迭代器。
二、可迭代对象与迭代器
1.可迭代对象
对象含有__iter__方法,就是可迭代对象。不含__iter__方法,也可能是可迭代对象。__getitem__方法会自动创建__iter__来满足遍历条件。
class Person: def __init__(self, persons): self.persons = persons # def __iter__(self): # return 1 def __getitem__(self, item): # 假如__iter__没有设定,解释器会继续查找__getitem__ return self.persons[item] # __getitem__会自动创建__iter___来进行迭代 per = Person(["Bob", "Tom", "Li", "Jan"]) print(per[:3]) for p in per: print(p) """ ['Bob', 'Tom', 'Li'] Bob Tom Li Jan """
2.迭代器
iter()用来将可迭代对象生成迭代器。
class Person: def __init__(self, persons): self.persons = persons def __getitem__(self, item): return self.persons[item] return len(self.persons) per = Person(["Bob", "Tom", "Li", "Jan"]) my = iter(per) for i in range(len(per)): print(next(my)) """ Bob Tom Li Jan """
3.迭代器的实现
当需要定义可迭代对象时,一般单独写一个迭代器功能。不建议在可迭代对象内写__next__
class MyIterator: def __init__(self, persons): self.persons = persons self.__index = 0 def __iter__(self): return self def __next__(self): try: per = self.persons[self.__index] except IndexError: raise StopIteration self.__index += 1 return per class Person: def __init__(self, persons): self.persons = persons def __len__(self): return len(self.persons) def __iter__(self): return MyIterator(self.persons) # 当要定义可迭代对象时,通常不在类内写__next__,而是写在外面 per = Person(["Bob", "Tom", "Li", "Jan"]) my = iter(per) for i in range(len(per)): print(next(my))
三、生成器
当函数中存在yield时,它就不再是普通的函数了,而是一个生成器函数。
def f1(): print("-----------step1----------") yield 1 print("-----------step2----------") yield 2 g = f1() # 这一步没执行 print(next(g)) print(next(g))
生成器的实现,本质上是对函数的堆存储方式进行了一层封装。
普通函数的调用和执行过程。被调用函数独在字节码这一步单独存储于PyCodeObject中。它必须一次性运行完,而不会保留执行时的栈帧。
import inspect frame = None def foo(): bar() def bar(): global frame frame = inspect.currentframe() foo() print(dis.dis(foo)) print(frame.f_code.co_name) print(frame.f_back.f_code.co_name, frame.f_back) # 所有的栈帧都是分配在对内存当中,独立于调用者存在
生成器对象(PyGenObject)会被编译成相应的字节码,以及每一次yield的栈帧和本地变量。相比于普通函数,PyGenObject多了对f_lasti和f_locals的保存。只要每次yield拿到f_lasti和gen_fn's bytecode,就可以暂停或继续执行生成器。
import dis def f1(): print("-----------step1----------") name = "Li" yield 1 print("-----------step2----------") age = 24 yield 2 g = f1() # 这一步没执行 print(g.gi_code) # 显示代码的起止位置 print(dis.dis(g)) # 显示栈帧与字节码 print(g.gi_frame.f_lasti, g.gi_frame.f_locals) # 显示代码执行至此的栈帧和本地变量集 print(next(g)) print(g.gi_frame.f_lasti, g.gi_frame.f_locals) print(next(g)) print(g.gi_frame.f_lasti, g.gi_frame.f_locals) """ <code object f1 at 0x110b71f60, file "<ipython-input-261-13e8f3cdc8da>", line 2> 3 0 LOAD_GLOBAL 0 (print) 2 LOAD_CONST 1 ('-----------step1----------') 4 CALL_FUNCTION 1 6 POP_TOP 4 8 LOAD_CONST 2 ('Li') 10 STORE_FAST 0 (name) 5 12 LOAD_CONST 3 (1) 14 YIELD_VALUE 16 POP_TOP 6 18 LOAD_GLOBAL 0 (print) 20 LOAD_CONST 4 ('-----------step2----------') 22 CALL_FUNCTION 1 24 POP_TOP 7 26 LOAD_CONST 5 (24) 28 STORE_FAST 1 (age) 8 30 LOAD_CONST 6 (2) 32 YIELD_VALUE 34 POP_TOP 36 LOAD_CONST 0 (None) 38 RETURN_VALUE None -1 {} -----------step1---------- 1 None 14 {'name': 'Li'} -----------step2---------- 2 32 {'name': 'Li', 'age': 24} """
待续。。。。。。