2.3 - 可迭代对象、迭代器和生成器
2.3.1 判断 可迭代对象和迭代器和生成器
可迭代对象不一定是迭代器,但是迭代器一定是可迭代对象。
可迭代对象:str;tuple;list;dict;set;forzenset;range。
返回一个迭代器:map;filter;reversed;zip;enumerate。
真正准确判断是否是迭代器的方法:
from typing import Iterable, Iterator lst = [1, 2, 3] print(isinstance(lst, Iterator)) # 判断实例对象lst是否是迭代器 print(issubclass(list, Iterator)) # 判断某个类实例化之后是否是 迭代器
但是:上述的两种判断方法并不准确,因为 isinstance和issubclass 在判断是否是可迭代对象的时候,只判断是否支持迭代协议(__iter__()),如果对象只实现了序列协议而没有实现迭代协议,尽管这个对象依旧是可迭代对象,但是会被判为False。
真正准确的判断是否是可迭代对象的方法:
# 判断 类或实例 是否是可迭代对象 lst = [1, 2, 3] class A: pass print('__iter__' in dir(lst) or '__getitem__' in dir(lst)) # 判断实例对象是否是可迭代对象 print('__iter__' in dir(A) or '__getitem__' in dir(A)) # 判断类是否是可迭代对象
2.3.2 可迭代对象
需要满足两个条件之一:
1)支持迭代协议(包含 __iter__() 方法)
2)支持序列协议(包含 __getitem__(self, item) 方法,且数字参数从0开始),该协议支持了 列表的索引、切片功能。
2.3.3 迭代器
必须同时满足下列两个条件:
1)实现 __iter__() 方法 【这也是 迭代器一定是可迭代对象的原因。】
2)实现 __next__() 方法
2.3.4 可迭代对象和迭代器 的迭代过程
# 迭代列表 lst = [3,5,8,6] for i in lst: print(i)
如上述案例所示:
对一个可迭代对象:
当执行for循环的时候,首先判断对象是不是 Iterable,如果不是则抛出 TypeError 异常。如果是,则会优先自动触发 __iter__() 方法,如果没有实现该方法,才会触发序列协议 __getitem__() 方法。
当执行 __getitem__(self,item) 方法的时候:将可迭代对象自身传递给self,下标索引 item从0开始,每次循环 item 的值加1,并且在上述案例中,每次循环返回的值都会赋值给 迭代元素 i。直到超出索引范围,抛出 StopIteration 异常,for循环捕获异常之后,迭代结束。
当执行 __iter__方法的时候:将可迭代对象自身传递给self,然后 __iter__方法会返回一个 迭代器对象,之后每次循环都会自动调用迭代器的 __next__() 方法,并将next()方法返回的值赋值给 迭代元素i。直到 __next__ 方法抛出 StopIteration 异常,for循环捕获异常之后,迭代结束。
对一个迭代器:
因为迭代器实现了迭代协议__iter__,所以 迭代器本身也是可以迭代的。当一个迭代器进行迭代的时候,同样会首先自动触发 __iter__ 方法,这时候__iter__ 方法会返回自身,之后调用自身的next方法执行后续过程。
2.3.5 生成器(特殊的迭代器)
通过列表生成式,我们可以创建一个列表:lst = [ x*x for x in range(5) ],但是,受到内存限制,列表容量肯定是有限的。如果列表元素可以按照某种算法推算出来,那我们可以在循环的过程中一边遍历一边推算后续的元素,这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
创建生成器的第一种方式:()
# 创建生成器的第一种方式,适用于 元素的推算逻辑比较简单的情况 # 通过 () ,列表生成式使用的是 [] g = (x*x for x in range(5)) for i in g: print(i)
列表生成式使用 [ ] 创建一个列表,生成器使用 ()创建一个生成器。
创建生成器的第二种方式:yield
# 创建生成器的第二种方式,适用于 元素的推算逻辑比较复杂的情况 def fib(max): n, a, b = 0, 0, 1 while n < max: yield a a, b = b, a+b n = n + 1
如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator。
generator函数和普通函数的区别:
普通函数是顺序执行,遇到return
语句或者最后一行函数语句返回;generator函数定义完成之后,在每次调用 next() 的时候执行,遇到 yield 语句返回,然后程序被挂起,直到再次调用 next() 函数时,从上次返回的 yield 语句处继续往下执行。
注:每调用一次generator函数就会新建一个生成器,不同的生成器之间互相独立,各自迭代各自的,互不影响。
生成器作为另一个函数的参数的时候,可以将生成器的表达式(创建方式一)直接传入作为参数,不必用 ()括起来。
# 生成器作为函数参数 sum( x*x for x in range(3) ) # 5
例:分下如下程序的结果:
def g(a, b): print(1) yield a + b print(2) yield a - b print(3) if __name__ == "__main__": for x in g(6, 2): print(x)
打印输出:1 8 2 2 3