迭代器和生成器
Iterator
迭代器(iterator
)必须提供.__next__()
方法.__next__()
执行要么返回下一个迭代器
要么抛出StopIteration
异常以标识迭代完成
Iterable
可迭代对象(Iterable
)是实现了迭代器协议的对象,它自身不一定是Iterator
。
它只要提供了可以返回一个迭代器的.__iter__()
方法,或者可以使用下标(subscriptable
)。
python 的内部工具(for
语句,sum()
函数,list()
函数等)使用迭代器协议访问可迭代对象。
for 循环机制
for
循环/list()
的等内部做的事情:
执行它的
.__iter__()
转为迭代器(如果
.__iter__
没有实现会尝试遍历下标,即__getitem__
,见面向对象魔术方法章节。这是旧的迭代器协议)迭代器
.__next__()
访问下一个迭代器捕获
StopIteration
异常(旧迭代器协议IndexError
异常)
事实上迭代器也需要实现.__iter__()
返回自身用来参与 for 循环
内置函数next()
iter()
next(Obj)
相当于 Obj.__next__()
iter(Obj)
相当于 Obj.__iter__()
,它也会尝试用旧的迭代器协议
>>> a = "1234"
>>> a = iter(a)
>>> next(a)
'1'
>>> next(a)
'2'
>>> a
<str_iterator object at 0x0000000002AD6438>
类似于之前闭包章节的累加函数,常见的内置对象创建的多个迭代器互不影响:
>>> a = "1234"
>>> b = iter(a)
>>> c = iter(a)
>>> next(b)
'1'
>>> next(b)
'2'
>>> next(c)
'1'
但是生成的迭代器会受到迭代器对象本身的影响(惰性):
a = [1, 2, 3]
b = iter(a)
next(b)
# 1
a[-1] = 0
next(b)
# 2
next(b)
# 0
所以for in
语句中改变被迭代对象本体会对循环产生影响。
声明迭代器类
创建一个返回数字的迭代器对象,初始值为 1,逐步递增 1
class MyCount:
def __init__(self, a=0):
self.a = a
def __iter__(self):
return self
def __next__(self):
x = self.a
self.a += 1
return x
c = MyCount()
print(next(c)) # 0
print(next(c)) # 1
print(next(c)) # 2
for n in c:
print(n, end="") # 3456789
if n == 9:
break
generator
在 python 中生成器(generator)是一种特殊的迭代器
在 python3 中,大部分内置函数如reversed
、map
、filter
等的返回值都可视为生成器,他们往往不立即计算内部直到开始迭代,且只能遍历一次,也无法用下标取值。但是内存节约了很多。
generator function
生成器函数也叫作惰性函数
使用yield
而不是 return
生成器函数执行返回一个生成器
生成器每次 next 返回一个生成器函数的yield
,函数的状态被保存了
def a():
yield 3
yield 4
yield 5
>>> b = a()
>>> next(b)
3
>>> next(b)
4
>>> next(b)
5
>>> b
<generator object a at 0x0000000002B002A0>
生成器表达式
类似以前讲过的列表解析 但是不是使用中括号,而是使用圆括号
# 前文的使用yield的惰性函数a等效于这个:
a = (i for i in range(3, 6))
生成器表达式占用的内存比列表解析小得多。避免超长的列表产生。
注意
在 with 语句中(如文件句柄管理)定义的迭代器/迭代器对象/生成器
由于惰性使用,在 with 语句关闭之后会导致其生成的可迭代对象 next 方法报错
其他生成器方法
除了.__next__()
方法,生成器还有相比于一般迭代器额外的方法:
.send()
方法.close()
方法.throw()
方法
.send()
的其参数将作为上一个 yield 语句的返回值来进行下一个__next__()
def a():
n = 1
while 1:
print(n := (n + (yield n)))
b = a()
print(next(b)) # 1 #也可以直接b.send(None)代替第一次的next(b)
b.send(5) # 6
b.send(5) # 11
b.send(5) # 16
b.send(5) # 21
b.close()
zip(*[iter(list)]*int)
这是一个比较巧妙的用法
>>> list(zip(*[iter([1,2,3,4,5,6,7,8,9])]*3))
[(1,2,3),(4,5,6),(7,8,9)]
itertools 模块
内置模块itertools
提供了非常有用的用于操作迭代对象的函数
itertools.count(1)
无限计数器itertools.cycle('吼啦迷迭吼啦哟')
无限迭代器itertools.repeat('呐', 3)
复读机 次数可选 默认无限itertools.chain('吼啦迷迭', '吼啦哟')
接头迭代器itertools.groupby('neeeee,mooooo')
分组迭代器 相邻的相等的一组
排列组合穷举
itertools.product('XY','xy')
笛卡尔积itertools.product('Xxy',repeat=n)
排列(有放回抽样 n 次)itertools.permutations('Xxy',n)
排列(不放回抽样 n 次)itertools.combinations('Xxy',n)
组合(不放回抽样 n 次)itertools.combinations_with_replacement
组合(有放回抽样 n 次)