Skip to content

迭代器和生成器

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__() ,它也会尝试用旧的迭代器协议

shell
>>> a = "1234"
>>> a = iter(a)
>>> next(a)
'1'
>>> next(a)
'2'

>>> a
<str_iterator object at 0x0000000002AD6438>

类似于之前闭包章节的累加函数,常见的内置对象创建的多个迭代器互不影响

shell
>>> a = "1234"
>>> b = iter(a)
>>> c = iter(a)
>>> next(b)
'1'
>>> next(b)
'2'
>>> next(c)
'1'

但是生成的迭代器会受到迭代器对象本身的影响(惰性):

python
a = [1, 2, 3]
b = iter(a)
next(b)
# 1
a[-1] = 0
next(b)
# 2
next(b)
# 0

所以for in语句中改变被迭代对象本体会对循环产生影响。

声明迭代器类

创建一个返回数字的迭代器对象,初始值为 1,逐步递增 1

python
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 中,大部分内置函数如reversedmapfilter等的返回值都可视为生成器,他们往往不立即计算内部直到开始迭代,且只能遍历一次,也无法用下标取值。但是内存节约了很多。

generator function

生成器函数也叫作惰性函数
使用yield而不是 return
生成器函数执行返回一个生成器
生成器每次 next 返回一个生成器函数的yield,函数的状态被保存了

shell
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>

生成器表达式

类似以前讲过的列表解析 但是不是使用中括号,而是使用圆括号

python
# 前文的使用yield的惰性函数a等效于这个:
a = (i for i in range(3, 6))

生成器表达式占用的内存比列表解析小得多。避免超长的列表产生。

注意

在 with 语句中(如文件句柄管理)定义的迭代器/迭代器对象/生成器
由于惰性使用,在 with 语句关闭之后会导致其生成的可迭代对象 next 方法报错

其他生成器方法

除了.__next__()方法,生成器还有相比于一般迭代器额外的方法:

  • .send()方法
  • .close()方法
  • .throw()方法

.send()的其参数将作为上一个 yield 语句的返回值来进行下一个__next__()

python
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)

这是一个比较巧妙的用法

shell
>>> 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 次)