所有生成器都是迭代器,因为生成器完全实现了迭代器接口,不过迭代器一般用于从集合取出元素,生成器用于 “凭空” 创造元素。斐波那契数列例子可以很好的说明两者区别:斐波那契数列中的数有无穷个,在一个集合里放不下。
在 Python 3 中,生成器有广泛用途。现在即使是内置的 range() 函数也要返回一个类似生成器的对象,而以前返回完整列表。如果一定让 range() 函数返回列表,必须明确指明(例如,list(range(100)))。
在 Python 中,所有集合都能迭代。在 Python 内部,迭代器用于支持:
- for 循环
- 构建和扩展集合类型
- 逐行遍历文本文件
- 列表推导,字典推导和集合推导
- 元组拆包
- 调用函数时,使用
*拆包
本章探讨以下话题:
- 语言内部使用 iter(...) 内置函数处理可迭代对象的方式
- 如何使用 Python 经典的迭代器模式
- 详细说明生成器函数的工作原理
- 如何使用生成器函数或生成器表达式代替经典的迭代器
- 如何使用标准库中通用的生成器函数
- 如何使用 yield from 语句合并生成器
- 案例分析: 在一个数据库转换工具中使用生成器处理大型数据集
- 为什么生成器和协程看似相同,其实差别很大,不能混淆
Sentence 类第 1 版:单词序列¶
我们创建一个类,并向它传入一些包含文本的字符串,然后可以逐个单词迭代,第 1 版要实现序列协议,这个类的对象可以迭代,因为所有序列都可以迭代 -- 这一点前面已经说过,现在说明真正的原因
下面展示了一个可以通过索引从文本提取单词的类:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
# 返回一个字符串列表,里面的元素是正则表达式的全部非重叠匹配
self.words = RE_WORD.findall(text)
def __getitem__(self, index):
return self.words[index]
# 为了完善序列协议,我们实现了 __len__ 方法,不过,为了让对象可迭代,没必要实现这个方法
def __len__(self):
return len(self.words)
def __repr__(self):
# 下面这个函数用于生成大型数据结构的简略字符串表示形式
return 'Sentence(%s)' % reprlib.repr(self.text)
s = Sentence('"The time has come,", the Walrus said')
s
for word in s:
print(word)
list(s)
s[0], s[-1]
我们都知道,序列可以迭代,下面说明具体原因: iter 函数
解释器需要迭代对象 x 时候,会自动调用 iter(x)
内置的 iter 函数有以下作用。
检查对象是否实现了
__iter__方法,如果实现了就调用它,获取一个迭代器如果没有实现
__iter__方法,但是实现了__getitem__方法,Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素如果尝试失败,Python 抛出 TypeError 异常,通常提示
C object is not iterable,其中 C 是目标对象所属的类
任何 Pytho 序列都可迭代的原因是实现了 __getitem__ 方法。其实标准的序列也都实现了 __iter__ 方法,因此我们也应该这么做。之所以对 __getitem__ 方法特殊处理,是为了向后兼容,未来可能不会再这么做
11 章提到过,这是鸭子类型的极端形式,不仅要实现特殊的 __iter__ 方法,还要实现 __getitem__ 方法,而且 __getitem__ 方法的参数是从 0 开始的整数(int),这样才认为对象是可迭代的。
在白鹅类型理论中,可迭代对象定义的简单一些,不过没那么灵活,如果实现了 __iter__ 方法,那么就认为对象是可迭代的。此时,不需要创建子类,也不需要注册,因为 abc.Iterable 类实现了 __subclasshook__ 方法,下面举个例子:
from collections import abc
class Foo:
def __iter__(self):
pass
issubclass(Foo, abc.Iterable)
f = Foo()
isinstance(f, abc.Iterable)
不过要注意,前面定义的 Sentence 类是可迭代的,却无法通过 issubclass(Sentence, abc.Iterable) 测试
从 Python 3.4 开始,检测对象 x 是否可迭代,最准确的方法是调用 iter(x) 函数,如果不可迭代,再处理 TypeError 异常,这回比使用 isinstance(x, abc.Iterable) 更准确,因为 iter(x) 会考虑到
__getitem__方法
迭代对象之前显式检查或许没必要,因为试图迭代不可迭代对象时,抛出的错误很明显。如果除了跑出 TypeError 异常之外还要进一步处理,可以使用 try/except 块,无需显式检查。如果要保存对象,等以后迭代,或许可以显式检查,因为这种情况需要尽早捕捉错误
可迭代对象与迭代器对比¶
可迭代对象:
使用 iter 内置函数可以获取迭代器对象。如果对象实现了能返回迭代器的 __iter__ 方法,那么对象可迭代。序列都可以迭代:实现了 __getitem__ 方法,而且其参数是从 0 开始的索引,这种对象也可以迭代。
我们要明确可迭代对象和迭代器之间的关系: Python 从可迭代的对象中获取迭代器
下面是一个 for 循环,迭代一个字符串,这里字符串 'ABC' 是可迭代对象,背后有迭代器,只是我们看不到
s = 'ABC'
for char in s:
print(char)
如果用 while 循环,要像下面这样:
s = 'ABC'
it = iter(s)
while True:
try:
print(next(it))
except StopIteration: # 这个异常表示迭代器到头了
del it
break
标准迭代器接口有两个方法:
__next__ 返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常
__iter__ 返回 self,以便在应该使用可迭代对象的地方使用迭代器,比如 for 循环
这个接口在 collections.abc.Iterator 抽象基类中,这个类定义了 __next__ 抽象方法,而且继承自 Iterable 类: __iter__ 抽象方法则在 Iterable 类中定义
abc.Iterator 抽象基类中
__subclasshook__的方法作用就是检查有没有__iter__和__next__属性检查对象 x 是否为 迭代器 的最好方式是调用 isinstance(x, abc.Iterator)。得益于
Iterator.__subclasshook__方法,即使对象 x 所属的类不是 Iterator 类的真实子类或虚拟子类,也能这样检查
下面可以看到 Sentence 类如何使用 iter 函数构建迭代器,和如何使用 next 函数使用迭代器
s3 = Sentence('Pig and Pepper')
it = iter(s3)
it
next(it)
next(it)
next(it)
next(it)
list(it) # 到头后,迭代器没用了
list(s3) # 如果想再次迭代,要重新构建迭代器
因为迭代器只需要 __next__ 和 __iter__ 两个方法,所以除了调用 next() 方法,以及捕获 StopIteration 异常之外,没有办法检查是否还有遗留元素。此外,也没有办法 ”还原“ 迭代器。如果想再次迭代,那就要调用 iter(...) 传入之前构造迭代器传入的可迭代对象。传入迭代器本身没用,因为前面说过 Iterator.__iter__ 方法实现方式是返回实例本身,所以传入迭代器无法还原已经耗尽的迭代器
我们可以得出迭代器定义如下:实现了无参数的 __next__ 方法,返回序列中的下一个元素,如果没有元素了,那么抛出 StopIteration 异常。Python 中迭代器还实现了 __iter__ 方法,因此迭代器也可以迭代。因为内置的 iter(...) 函数会对序列做特殊处理,所以第 1 版 的 Sentence 类可以迭代。
Sentence 类第 2 版:典型的迭代器¶
这一版根据《设计模式:可复用面向对象软件的基础》一书给出的模型,实现典型的迭代器设计模式。注意,这不符合 Python 的习惯做法,后面重构时候会说明原因。不过,通过这一版能明确可迭代集合和迭代器对象之间的区别
下面的类可以迭代,因为实现了 __iter__ 方法,构建并返回一个 SentenceIterator 实例,《设计模式:可复用面向对象软件的基础》一书就是这样描述迭代器设计模式的。
这里之所以这么做,是为了清楚的说明可迭代的对象和迭代器之间的重要区别,以及二者间的联系。
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
return SentenceIterator(self.words)
class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration
self.index += 1
return word
def __iter__(self):
return self
注意,对于这个例子来说,没有必要在 SentenceIterator 类中实现 __iter__ 方法,不过这么做是对的,因为迭代器应该实现 __next__ 和 __iter__ 两个方法,而且这么做能让迭代器通过 issubclass(SentenceInterator, abc.Iterator) 测试。如果让 SentenceIterator 继承 abc.Iterator 类,那么它会继承 abc.Iterator.__iter__ 这个具体方法
注意 SentenceIterator 类的大多数代码在处理迭代器内部状态,稍后会说明如何简化,不过我们先讨论一个看似合理实则错误的实现捷径
把 Sentence 变成迭代器:坏主意¶
构建可迭代的对象和迭代器经常出现错误,原因是混淆了二者。要知道,可迭代对象有个 __iter__ 方法,每次实例化一个新的迭代器,迭代器要实现 __next__ 方法,返回单个元素,此外要实现 __iter__ 方法,返回迭代器本身。
因此,迭代器可以迭代,但是可迭代的对象不是迭代器
除了 __iter__ 方法之外,你可能还想在 Sentence 类中实现 __next__ 方法,让 Sentence 实例既是可迭代对象,也是自身迭代器,可是这种想法非常糟糕,这也是常见的反模式
迭代器模式可以用来:
- 访问一个聚合对象的内容而无需暴露它的内部表示
- 支持对聚合对象的多种遍历
- 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)
为了“支持多种遍历”,必须能从同一个迭代的实例中获取多个独立的迭代器,而且各个迭代器要能维护自身的内部状态,因此这一模式正确的实现方法是,每次调用 iter(my_iterable) 都新建一个独立的迭代器,这就是为什么这个示例需要定义 SentenceIterator 类
可迭代对象一定不能是自身的迭代器,也就是说,可迭代对象必须实现
__iter__方法,但不能实现__next__方法。另一方面,迭代器应该可以一直迭代,迭代器的__iter__应该返回自身
Sentence 类第 3 版:生成器函数¶
实现同样功能,却符合 Python 习惯的方式是,用生成器函数替代 SentenceIterator 类。先看下面的例子:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
for word in self.words:
yield word
# 这个 return 不是必要的,生成器函数不会抛出 StopIteration 异常,
#而是在生成全部值之后直接退出
return
a = Sentence('hello world')
one = iter(a)
print(next(one))
two = iter(a)
print(next(two)) # 两个迭代器之间不会互相干扰
在这个例子中,迭代器其实是生成器对象,每次调用 __iter__ 方法都会自动创建,因为这里的 __iter__ 方法是生成器函数
生成器函数的工作原理¶
只要 Python 函数定义体中有 yield 关键字,该函数就是生成器函数,调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂
下面用一个特别简单的函数说明生成器行为:
def gen_123():
yield 1
yield 2
yield 3
gen_123
gen_123()
for i in gen_123():
print(i)
g = gen_123()
next(g)
next(g)
next(g)
next(g) # 生成器函数定义体执行完毕后,跑出 StopIteration 异常
生成器函数会创建一个生成器对象,包装生成器函数的定义体。把生成器传给 next(..) 函数时,生成器函数会向前,执行函数定义体中的下一个 yield 语句,返回产出的值,并在函数定义体的当前位置暂停。最终函数的定义体返回时,外层的生成器对象会抛出 StopIteration 异常 -- 这一点与迭代器协议一致
下面例子更清楚的说明了生成器函数定义体的执行过程:
def gen_AB():
print('start')
yield 'A'
print('continue')
yield 'B'
print('end')
for c in gen_AB():
print('-->', c)
现在在我们应该知道 Sentence.__iter__ 作用了: __iter__ 方法是生成器函数,调用时会构建一个实现了迭代器接口的生成器对象,因此不用再定义 SentenceIterator 类了。
这一版 Sentence 类比之前简短多了,但还不够懒惰,懒惰实现是指尽可能延后生成值,这样能节省内存,或许还可以避免做无用的处理
Sentence 类第 4 版:惰性实现¶
设计 Iterator 接口时考虑了惰性:next(my_iterator) 一次生成一个元素。惰性求值和及早求值是编程语言理论的技术术语
目前的 Sentence 类不具有惰性,因为 __init__ 方法急迫的构建好了文本中的单词列表,然后绑定到 self.words 属性上。这样就得到处理后的整个文本,列表使用的内存量可能与文本本身一样多(获取更多,这取决于文本中有多少非单词字符)。如果只需迭代前几个单词,大多数工作都是白费力气。
re.finditer 函数是 re.findall 函数的惰性版本,返回的不是列表,而是一个生成器,按需生成 re.MatchObject 实例。如果有很多匹配,re.finditer 能节省大量内存。如果我们要使用这个函数让上一版 Sentence 类变得懒惰,即只在需要时才生成下一个单词。代码如下所示:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
for match in RE_WORD.finditer(self.text):
yield match.group() # 从 MatchObject 实例中提取匹配正则表达式的具体文本
生成器表达式¶
简单的生成器函数,如前面的例子中使用的那个,可以替换成生成器表达式
生成器表达式可以理解为列表推导式的惰性版本:不会迫切的构建列表,而是返回一共额生成器,按需惰性产称元素。也就是说,如果列表推导是制造列表的工厂,那么生成器表达式是制造生成器的工厂
下面展示了一个生成器表达式,并与列表推导式对比:
def gen_AB():
print('start')
yield 'A'
print('continue')
yield 'B'
print('end')
res1 = [x * 3 for x in gen_AB()]
for i in res1:
print('-->', i)
res2 = (x * 3 for x in gen_AB())
res2
for i in res2:
print('-->', i)
可以看出,生成器表达式会产出生成器,因此可以使用生成器表达式进一步减少 Sentence 类的代码:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
self.text = text
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
return (match.group() for match in RE_WORD.finditer(self.text))
这里用的是生成器表达式构建生成器,然后将其返回,不过最终效果一样:调用 __iter__ 方法会得到一个生成器对象
生成器表达式是语法糖:完全可以替换成生成器函数,不过有时使用生成器表达式更加便利
何时使用生成器表达式¶
遇到简单的情况,可以使用成器表达式,因为因为这样扫一眼就知道代码作用
如果生成器表达式要分成多行,最好使用生成器函数,提高可读性
如果函数或构造方法只有一个参数,传入生成器表达式时不用写一堆调用函数的括号,再写一堆括号围住生成器表达式,只写一对括号就行,如果生成器表达式后面还有其他参数,那么必须使用括号围住,否则会抛出 SynataxError 异常
另一个例子:等差数列生成器¶
class ArithmeticProgression:
def __init__(self, begin, step, end=None):
self.begin = begin
self.step = step
self.end = end # 无穷数列
def __iter__(self):
# self 赋值给 result,不过要先强制转成前面加法表达式类型(两个支持加法的对象返回一个对象)
result = type(self.begin + self.step)(self.begin)
forever = self.end is None
index = 0
while forever or result < self.end:
yield result
index += 1
result = self.begin + self.step * index
ap = ArithmeticProgression(0, 1, 3)
list(ap)
ap = ArithmeticProgression(1, 5, 3)
list(ap)
ap = ArithmeticProgression(0, 1 / 3, 1)
list(ap)
上面的类完全可以用一个生成器函数代替
def aritprog_gen(begin, step, end=None):
result = type(begin + step)(begin)
forever = end is None
index = 0
while forever or result < end:
yield result
index += 1
result = begin + step * index
上面的实现很棒,但是要记住,标准库中有很多现成的生成器,下面会用 itertools 模块实现,这个版本更棒
使用 itertools 生成等差数列¶
itertools 提供了 19 个生成器函数,结合起来很有意思。
例如 itertools.count 函数返回的生成器能生成多个数。如果不传入参数,itertools.count 函数会生成从 0 开始的整数数列。不过,我们可以提供 start 和 step 值,这样实现的作用与 aritprog_gen 函数相似
import itertools
gen = itertools.count(1, .5)
next(gen)
next(gen)
next(gen)
next(gen)
然而 itertools.count 函数从不停止,因此,调用 list(count())) 会产生一个特别大的列表,超出可用的内存
不过,itertools.takewhile 函数不同,他会生成一个使用另一个生成器的生成器,在指定条件计算结果为 False 时候停止,因此,可以把这两个函数结合:
gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5))
list(gen)
所以,我们可以将等差数列写成这样:
import itertools
def aritprog_gen(begin, step, end=None):
first = type(begin+step)(begin)
ap_gen = itertools.count(first, step)
if end is not None:
ap_gen = itertools.takewhile(lambda n: n < end, ap_gen)
return ap_gen
注意, aritprog_gen 不是生成器函数,因为没有 yield 关键字,但是会返回一个生成器,因此它和其他的生成器函数一样,是一个生成器工厂函数
标准库中的生成器函数¶
标准库中有很多生成器,有用于逐行迭代文本文件的对象,还有出色的 os.walk 函数,不过本节专注于通用的函数:参数为任意可迭代对象,返回值是生成器,用于生成选中的,计算出的和重新排列的元素。
第一组是过滤生成器函数,如下:
def vowel(c):
return c.lower() in 'aeiou'
# 字符串各个元素传给 vowel 函数,为真则返回对应元素
list(filter(vowel, 'Aardvark'))
import itertools
# 与上面相反
list(itertools.filterfalse(vowel, 'Aardvark'))
# 处理 字符串,跳过 vowel 为真的元素,然后产出剩余的元素,不再检查
list(itertools.dropwhile(vowel, 'Aardvark'))
#返回真值对应的元素,立即停止,不再检查
list(itertools.takewhile(vowel, 'Aardvark'))
# 并行处理两个迭代对象,如果第二个是真值,则返回第一个
list(itertools.compress('Aardvark', (1, 0, 1, 1, 0, 1)))
list(itertools.islice('Aardvark', 4))
list(itertools.islice('Aardvark', 4, 7))
list(itertools.islice('Aardvark', 1, 7, 2))
下面是映射生成器函数:
sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
import itertools
# 产出累计的总和
list(itertools.accumulate(sample))
# 如果提供了函数,那么把前两个元素给他,然后把计算结果和下一个元素给它,以此类推
list(itertools.accumulate(sample, min))
list(itertools.accumulate(sample, max))
import operator
list(itertools.accumulate(sample, operator.mul)) # 计算乘积
list(itertools.accumulate(range(1, 11), operator.mul))
list(enumerate('albatroz', 1)) #从 1 开始,为字母编号
import operator
list(map(operator.mul, range(11), range(11)))
# 计算两个可迭代对象中对应位置的两个之和,元素最少的迭代完毕就停止
list(map(operator.mul, range(11), [2, 4, 8]))
list(map(lambda a, b: (a, b), range(11), [2, 4, 8]))
import itertools
# starmap 把第二个参数的每个元素传给第一个函数 func,产出结果,
# 输入的可迭代对象应该产出可迭代对象 iit,
# 然后以(func(*iit) 这种形式调用 func)
list(itertools.starmap(operator.mul, enumerate('albatroz', 1)))
sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
# 计算平均值
list(itertools.starmap(lambda a, b: b / a,
enumerate(itertools.accumulate(sample), 1)))
接下来是用于合并的生成器函数:
# 先产生第一个元素,然后产生第二个参数的所有元素,以此类推,无缝连接到一起
list(itertools.chain('ABC', range(2)))
list(itertools.chain(enumerate('ABC')))
# chain.from_iterable 函数从可迭代对象中获取每个元素,
# 然后按顺序把元素连接起来,前提是各个元素本身也是可迭代对象
list(itertools.chain.from_iterable(enumerate('ABC')))
list(zip('ABC', range(5), [10, 20, 30, 40])) #只要有一个生成器到头,就停止
# 处理到最长的迭代器到头,短的会填充 None
list(itertools.zip_longest('ABC', range(5)))
list(itertools.zip_longest('ABC', range(5), fillvalue='?')) # 填充问号
itertools.product 生成器是计算笛卡尔积的惰性方式,从输入的各个迭代对象中获取元素,合并成由 N 个元素构成的元组,与嵌套的 for 循环效果一样。repeat指明重复处理多少次可迭代对象。下面演示 itertools.product 的用法
list(itertools.product('ABC', range(2)))
suits = 'spades hearts diamonds clubs'.split()
list(itertools.product('AK', suits))
# 传入一个可迭代对象,产生一系列只有一个元素的元祖,不是特别有用
list(itertools.product('ABC'))
# repeat = N 重复 N 次处理各个可迭代对象
list(itertools.product('ABC', repeat=2))
list(itertools.product(range(2), repeat=3))
rows = itertools.product('AB', range(2), repeat=2)
for row in rows: print(row)
把输入的各个元素扩展成多个输出元素的生成器函数:
ct = itertools.count()
next(ct) # 不能构建 ct 列表,因为 ct 是无穷的
next(ct), next(ct), next(ct)
list(itertools.islice(itertools.count(1, .3), 3))
cy = itertools.cycle('ABC')
next(cy)
list(itertools.islice(cy, 7))
rp = itertools.repeat(7) # 重复出现指定元素
next(rp), next(rp)
list(itertools.repeat(8, 4)) # 4 次数字 8
list(map(operator.mul, range(11), itertools.repeat(5)))
itertools 中 combinations, comb 和 permutations 生成器函数,连同 product 函数称为组合生成器。itertool.product 和其余组合学函数有紧密关系,如下:
# 'ABC' 中每两个元素 len() == 2 的各种组合
list(itertools.combinations('ABC', 2))
# 包括相同元素的每两个元素的各种组合
list(itertools.combinations_with_replacement('ABC', 2))
# 每两个元素的各种排列
list(itertools.permutations('ABC', 2))
list(itertools.product('ABC', repeat=2))
用于重新排列元素的生成器函数:
# 产出由两个元素组成的元素,形式为 (key, group),其中 key 是分组标准,
#group 是生成器,用于产出分组里的元素
list(itertools.groupby('LLLAAGGG'))
for char, group in itertools.groupby('LLLLAAAGG'):
print(char, '->', list(group))
animals = ['duck', 'eagle', 'rat', 'giraffe', 'bear',
'bat', 'dolphin', 'shark', 'lion']
animals.sort(key=len)
animals
for length, group in itertools.groupby(animals, len):
print(length, '->', list(group))
# 使用 reverse 生成器从右往左迭代 animals
for length, group in itertools.groupby(reversed(animals), len):
print(length, '->', list(group))
# itertools 产生多个生成器,每个生成器都产出输入的各个元素
list(itertools.tee('abc'))
g1, g2 = itertools.tee('abc')
next(g1)
next(g2)
next(g2)
list(g1)
list(g2)
list(zip(*itertools.tee('ABC')))
Python 3.3 中新语法 yield from¶
如果生成器函数需要产生两一个生成器生成的值,传统方法是使用 for 循环
def chain(*iterables): # 自己写的 chain 函数,标准库中的 chain 是用 C 写的
for it in iterables:
for i in it:
yield i
s = 'ABC'
t = tuple(range(3))
list(chain(s, t))
chain 生成器函数把操作依次交给接收到的各个可迭代对象处理。为此 Python 3.3 引入了新语法,如下:
def chain(*iterables):
for i in iterables:
yield from i # 详细语法在 16 章讲
list(chain(s, t))
可迭代的归约函数¶
接受可迭代对象,然后返回单个结果,叫归约函数。
all([1, 2, 3]) # 所有元素为真返回 True
all([1, 0, 3])
any([1, 2, 3]) # 有元素为真就返回 True
any([1, 0, 3])
any([0, 0, 0])
any([])
g = (n for n in [0, 0.0, 7, 8])
any(g)
next(g) # any 碰到一个为真就不往下判断了
还有一个内置的函数接受一个可迭代对象,返回不同的值 -- sorted,reversed 是生成器函数,与此不同,sorted 会构建并返回真正的列表,毕竟要读取每一个元素才能排序。它返回的是一个排好序的列表。这里提到 sorted,是因为它可以处理任何可迭代对象
当然,sorted 和这些归约函数只能处理最终会停止的可迭代对象,这些函数会一直收集元素,永远无法返回结果
深入分析 iter 函数¶
iter 函数还有一个鲜为人知的用法:传两个参数,使用常规的函数或任何可调用的对象创建迭代器。这样使用时,第一个参数必须是可调用对象,用于不断调用(没有参数),产出各个值,第二个是哨符,是个标记值,当可调用对象返回这个值时候,触发迭代器抛 出 StopIteration 异常,而不产出哨符。
下面是掷骰子,直到掷出 1
from random import randint
def d6():
return randint(1, 6)
d6_iter = iter(d6, 1)
d6_iter
for roll in d6_iter:
print(roll)
内置函数 iter 的文档有一个实用的例子,逐行读取文件,直到遇到空行或者到达文件末尾为止:
# for line in iter(fp.readline, '\n'):
# process_line(line)
把生成器当成协程¶
Python 2.2 引入了 yield 关键字实现的生成器函数,Python 2.5 为生成器对象添加了额外的方法和功能,其中最引人关注的是 .send() 方法
与 .__next__() 方法一样,.send() 方法致使生成器前进到下一个 yield 语句。不过 send() 方法还允许使用生成器的客户把数据发给自己,即不管传给 .send() 方法什么参数,那个参数都会成为生成器函数定义体中对应的 yield 表达式的值。也就是说,.send() 方法允许在客户代码和生成器之间双向交换数据。而 .__next__() 方法只允许客户从生成器中获取数据
这是一项重要的 “改进”,甚至改变了生成器本性,这样使用的话,生成器就变成了协程。所以要提醒一下:
- 生成器用于生成供迭代的数据
- 协程是数据的消费者
- 为了避免脑袋爆炸,不能把两个概念混为一谈
- 协程与迭代无关
- 注意,虽然在协程中会使用 yield 产出值,但这与迭代无关
延伸阅读¶
有个简单的生成器函数例子
def f():
x=0
while True:
x += 1
yield x
我们无法通过函数调用抽象产出这个过程,下面似乎能抽象产出这个过程:
def f():
def do_yield(n):
yield n
x = 0
while True:
x += 1
do_yield(x)
调用 f() 会得到一个死循环,而不是生成器,因为 yield 只能将最近的外层函数变成生成器函数。虽然生成器函数看起来像函数,可是我们不能通过简单的函数调用把职责委托给另一个生成器函数。
Python 新引入的 yield from 语法允许生成器或协程把工作委托给第三方完成,这样就无需嵌套 for 循环作为变通了。在函数调用前面加上 yield from 能 ”解决“ 上面的问题,如下:
def f():
def do_yield(n):
yield n
x = 0
while True:
x += 1
yield from do_yield(x)