所有生成器都是迭代器,因为生成器完全实现了迭代器接口,不过迭代器一般用于从集合取出元素,生成器用于 “凭空” 创造元素。斐波那契数列例子可以很好的说明两者区别:斐波那契数列中的数有无穷个,在一个集合里放不下。
在 Python 3 中,生成器有广泛用途。现在即使是内置的 range() 函数也要返回一个类似生成器的对象,而以前返回完整列表。如果一定让 range() 函数返回列表,必须明确指明(例如,list(range(100)))。
在 Python 中,所有集合都能迭代。在 Python 内部,迭代器用于支持:
* 拆包本章探讨以下话题:
我们创建一个类,并向它传入一些包含文本的字符串,然后可以逐个单词迭代,第 1 版要实现序列协议,这个类的对象可以迭代,因为所有序列都可以迭代 -- 这一点前面已经说过,现在说明真正的原因
下面展示了一个可以通过索引从文本提取单词的类:
In [3]:
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)
In [4]:
s = Sentence('"The time has come,", the Walrus said')
s
Out[4]:
In [5]:
for word in s:
print(word)
In [6]:
list(s)
Out[6]:
In [7]:
s[0], s[-1]
Out[7]:
我们都知道,序列可以迭代,下面说明具体原因: 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__ 方法,下面举个例子:
In [1]:
from collections import abc
class Foo:
def __iter__(self):
pass
issubclass(Foo, abc.Iterable)
Out[1]:
In [2]:
f = Foo()
isinstance(f, abc.Iterable)
Out[2]:
不过要注意,前面定义的 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' 是可迭代对象,背后有迭代器,只是我们看不到
In [10]:
s = 'ABC'
for char in s:
print(char)
如果用 while 循环,要像下面这样:
In [11]:
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 函数使用迭代器
In [13]:
s3 = Sentence('Pig and Pepper')
it = iter(s3)
it
Out[13]:
In [14]:
next(it)
Out[14]:
In [15]:
next(it)
Out[15]:
In [16]:
next(it)
Out[16]:
In [17]:
next(it)
In [18]:
list(it) # 到头后,迭代器没用了
Out[18]:
In [20]:
list(s3) # 如果想再次迭代,要重新构建迭代器
Out[20]:
因为迭代器只需要 __next__ 和 __iter__ 两个方法,所以除了调用 next() 方法,以及捕获 StopIteration 异常之外,没有办法检查是否还有遗留元素。此外,也没有办法 ”还原“ 迭代器。如果想再次迭代,那就要调用 iter(...) 传入之前构造迭代器传入的可迭代对象。传入迭代器本身没用,因为前面说过 Iterator.__iter__ 方法实现方式是返回实例本身,所以传入迭代器无法还原已经耗尽的迭代器
我们可以得出迭代器定义如下:实现了无参数的 __next__ 方法,返回序列中的下一个元素,如果没有元素了,那么抛出 StopIteration 异常。Python 中迭代器还实现了 __iter__ 方法,因此迭代器也可以迭代。因为内置的 iter(...) 函数会对序列做特殊处理,所以第 1 版 的 Sentence 类可以迭代。
这一版根据《设计模式:可复用面向对象软件的基础》一书给出的模型,实现典型的迭代器设计模式。注意,这不符合 Python 的习惯做法,后面重构时候会说明原因。不过,通过这一版能明确可迭代集合和迭代器对象之间的区别
下面的类可以迭代,因为实现了 __iter__ 方法,构建并返回一个 SentenceIterator 实例,《设计模式:可复用面向对象软件的基础》一书就是这样描述迭代器设计模式的。
这里之所以这么做,是为了清楚的说明可迭代的对象和迭代器之间的重要区别,以及二者间的联系。
In [2]:
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 类的大多数代码在处理迭代器内部状态,稍后会说明如何简化,不过我们先讨论一个看似合理实则错误的实现捷径
构建可迭代的对象和迭代器经常出现错误,原因是混淆了二者。要知道,可迭代对象有个 __iter__ 方法,每次实例化一个新的迭代器,迭代器要实现 __next__ 方法,返回单个元素,此外要实现 __iter__ 方法,返回迭代器本身。
因此,迭代器可以迭代,但是可迭代的对象不是迭代器
除了 __iter__ 方法之外,你可能还想在 Sentence 类中实现 __next__ 方法,让 Sentence 实例既是可迭代对象,也是自身迭代器,可是这种想法非常糟糕,这也是常见的反模式
迭代器模式可以用来:
为了“支持多种遍历”,必须能从同一个迭代的实例中获取多个独立的迭代器,而且各个迭代器要能维护自身的内部状态,因此这一模式正确的实现方法是,每次调用 iter(my_iterable) 都新建一个独立的迭代器,这就是为什么这个示例需要定义 SentenceIterator 类
可迭代对象一定不能是自身的迭代器,也就是说,可迭代对象必须实现
__iter__方法,但不能实现__next__方法。另一方面,迭代器应该可以一直迭代,迭代器的__iter__应该返回自身
实现同样功能,却符合 Python 习惯的方式是,用生成器函数替代 SentenceIterator 类。先看下面的例子:
In [13]:
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
In [21]:
a = Sentence('hello world')
one = iter(a)
print(next(one))
two = iter(a)
print(next(two)) # 两个迭代器之间不会互相干扰
In [22]:
def gen_123():
yield 1
yield 2
yield 3
gen_123
Out[22]:
In [23]:
gen_123()
Out[23]:
In [24]:
for i in gen_123():
print(i)
In [25]:
g = gen_123()
next(g)
Out[25]:
In [26]:
next(g)
Out[26]:
In [27]:
next(g)
Out[27]:
In [28]:
next(g) # 生成器函数定义体执行完毕后,跑出 StopIteration 异常
生成器函数会创建一个生成器对象,包装生成器函数的定义体。把生成器传给 next(..) 函数时,生成器函数会向前,执行函数定义体中的下一个 yield 语句,返回产出的值,并在函数定义体的当前位置暂停。最终函数的定义体返回时,外层的生成器对象会抛出 StopIteration 异常 -- 这一点与迭代器协议一致
下面例子更清楚的说明了生成器函数定义体的执行过程:
In [29]:
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 类比之前简短多了,但还不够懒惰,懒惰实现是指尽可能延后生成值,这样能节省内存,或许还可以避免做无用的处理
设计 Iterator 接口时考虑了惰性:next(my_iterator) 一次生成一个元素。惰性求值和及早求值是编程语言理论的技术术语
目前的 Sentence 类不具有惰性,因为 __init__ 方法急迫的构建好了文本中的单词列表,然后绑定到 self.words 属性上。这样就得到处理后的整个文本,列表使用的内存量可能与文本本身一样多(获取更多,这取决于文本中有多少非单词字符)。如果只需迭代前几个单词,大多数工作都是白费力气。
re.finditer 函数是 re.findall 函数的惰性版本,返回的不是列表,而是一个生成器,按需生成 re.MatchObject 实例。如果有很多匹配,re.finditer 能节省大量内存。如果我们要使用这个函数让上一版 Sentence 类变得懒惰,即只在需要时才生成下一个单词。代码如下所示:
In [30]:
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 实例中提取匹配正则表达式的具体文本
In [3]:
def gen_AB():
print('start')
yield 'A'
print('continue')
yield 'B'
print('end')
res1 = [x * 3 for x in gen_AB()]
In [4]:
for i in res1:
print('-->', i)
In [5]:
res2 = (x * 3 for x in gen_AB())
res2
Out[5]:
In [6]:
for i in res2:
print('-->', i)
可以看出,生成器表达式会产出生成器,因此可以使用生成器表达式进一步减少 Sentence 类的代码:
In [7]:
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))
In [10]:
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
In [11]:
ap = ArithmeticProgression(0, 1, 3)
list(ap)
Out[11]:
In [12]:
ap = ArithmeticProgression(1, 5, 3)
list(ap)
Out[12]:
In [13]:
ap = ArithmeticProgression(0, 1 / 3, 1)
list(ap)
Out[13]:
上面的类完全可以用一个生成器函数代替
In [14]:
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
In [18]:
import itertools
gen = itertools.count(1, .5)
next(gen)
Out[18]:
In [19]:
next(gen)
Out[19]:
In [20]:
next(gen)
Out[20]:
In [21]:
next(gen)
Out[21]:
然而 itertools.count 函数从不停止,因此,调用 list(count())) 会产生一个特别大的列表,超出可用的内存
不过,itertools.takewhile 函数不同,他会生成一个使用另一个生成器的生成器,在指定条件计算结果为 False 时候停止,因此,可以把这两个函数结合:
In [24]:
gen = itertools.takewhile(lambda n: n < 3, itertools.count(1, .5))
list(gen)
Out[24]:
所以,我们可以将等差数列写成这样:
In [26]:
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
In [29]:
def vowel(c):
return c.lower() in 'aeiou'
# 字符串各个元素传给 vowel 函数,为真则返回对应元素
list(filter(vowel, 'Aardvark'))
Out[29]:
In [32]:
import itertools
# 与上面相反
list(itertools.filterfalse(vowel, 'Aardvark'))
Out[32]:
In [33]:
# 处理 字符串,跳过 vowel 为真的元素,然后产出剩余的元素,不再检查
list(itertools.dropwhile(vowel, 'Aardvark'))
Out[33]:
In [35]:
#返回真值对应的元素,立即停止,不再检查
list(itertools.takewhile(vowel, 'Aardvark'))
Out[35]:
In [37]:
# 并行处理两个迭代对象,如果第二个是真值,则返回第一个
list(itertools.compress('Aardvark', (1, 0, 1, 1, 0, 1)))
Out[37]:
In [38]:
list(itertools.islice('Aardvark', 4))
Out[38]:
In [39]:
list(itertools.islice('Aardvark', 4, 7))
Out[39]:
In [40]:
list(itertools.islice('Aardvark', 1, 7, 2))
Out[40]:
下面是映射生成器函数:
In [43]:
sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
import itertools
# 产出累计的总和
list(itertools.accumulate(sample))
Out[43]:
In [45]:
# 如果提供了函数,那么把前两个元素给他,然后把计算结果和下一个元素给它,以此类推
list(itertools.accumulate(sample, min))
Out[45]:
In [46]:
list(itertools.accumulate(sample, max))
Out[46]:
In [47]:
import operator
list(itertools.accumulate(sample, operator.mul)) # 计算乘积
Out[47]:
In [48]:
list(itertools.accumulate(range(1, 11), operator.mul))
Out[48]:
In [49]:
list(enumerate('albatroz', 1)) #从 1 开始,为字母编号
Out[49]:
In [50]:
import operator
list(map(operator.mul, range(11), range(11)))
Out[50]:
In [52]:
# 计算两个可迭代对象中对应位置的两个之和,元素最少的迭代完毕就停止
list(map(operator.mul, range(11), [2, 4, 8]))
Out[52]:
In [57]:
list(map(lambda a, b: (a, b), range(11), [2, 4, 8]))
Out[57]:
In [58]:
import itertools
# starmap 把第二个参数的每个元素传给第一个函数 func,产出结果,
# 输入的可迭代对象应该产出可迭代对象 iit,
# 然后以(func(*iit) 这种形式调用 func)
list(itertools.starmap(operator.mul, enumerate('albatroz', 1)))
Out[58]:
In [59]:
sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
# 计算平均值
list(itertools.starmap(lambda a, b: b / a,
enumerate(itertools.accumulate(sample), 1)))
Out[59]:
接下来是用于合并的生成器函数:
In [63]:
# 先产生第一个元素,然后产生第二个参数的所有元素,以此类推,无缝连接到一起
list(itertools.chain('ABC', range(2)))
Out[63]:
In [64]:
list(itertools.chain(enumerate('ABC')))
Out[64]:
In [66]:
# chain.from_iterable 函数从可迭代对象中获取每个元素,
# 然后按顺序把元素连接起来,前提是各个元素本身也是可迭代对象
list(itertools.chain.from_iterable(enumerate('ABC')))
Out[66]:
In [67]:
list(zip('ABC', range(5), [10, 20, 30, 40])) #只要有一个生成器到头,就停止
Out[67]:
In [68]:
# 处理到最长的迭代器到头,短的会填充 None
list(itertools.zip_longest('ABC', range(5)))
Out[68]:
In [70]:
list(itertools.zip_longest('ABC', range(5), fillvalue='?')) # 填充问号
Out[70]:
itertools.product 生成器是计算笛卡尔积的惰性方式,从输入的各个迭代对象中获取元素,合并成由 N 个元素构成的元组,与嵌套的 for 循环效果一样。repeat指明重复处理多少次可迭代对象。下面演示 itertools.product 的用法
In [79]:
list(itertools.product('ABC', range(2)))
Out[79]:
In [80]:
suits = 'spades hearts diamonds clubs'.split()
list(itertools.product('AK', suits))
Out[80]:
In [81]:
# 传入一个可迭代对象,产生一系列只有一个元素的元祖,不是特别有用
list(itertools.product('ABC'))
Out[81]:
In [82]:
# repeat = N 重复 N 次处理各个可迭代对象
list(itertools.product('ABC', repeat=2))
Out[82]:
In [83]:
list(itertools.product(range(2), repeat=3))
Out[83]:
In [86]:
rows = itertools.product('AB', range(2), repeat=2)
for row in rows: print(row)
把输入的各个元素扩展成多个输出元素的生成器函数:
In [88]:
ct = itertools.count()
next(ct) # 不能构建 ct 列表,因为 ct 是无穷的
Out[88]:
In [89]:
next(ct), next(ct), next(ct)
Out[89]:
In [90]:
list(itertools.islice(itertools.count(1, .3), 3))
Out[90]:
In [91]:
cy = itertools.cycle('ABC')
next(cy)
Out[91]:
In [92]:
list(itertools.islice(cy, 7))
Out[92]:
In [93]:
rp = itertools.repeat(7) # 重复出现指定元素
next(rp), next(rp)
Out[93]:
In [94]:
list(itertools.repeat(8, 4)) # 4 次数字 8
Out[94]:
In [95]:
list(map(operator.mul, range(11), itertools.repeat(5)))
Out[95]:
itertools 中 combinations, comb 和 permutations 生成器函数,连同 product 函数称为组合生成器。itertool.product 和其余组合学函数有紧密关系,如下:
In [96]:
# 'ABC' 中每两个元素 len() == 2 的各种组合
list(itertools.combinations('ABC', 2))
Out[96]:
In [97]:
# 包括相同元素的每两个元素的各种组合
list(itertools.combinations_with_replacement('ABC', 2))
Out[97]:
In [98]:
# 每两个元素的各种排列
list(itertools.permutations('ABC', 2))
Out[98]:
In [99]:
list(itertools.product('ABC', repeat=2))
Out[99]:
用于重新排列元素的生成器函数:
In [101]:
# 产出由两个元素组成的元素,形式为 (key, group),其中 key 是分组标准,
#group 是生成器,用于产出分组里的元素
list(itertools.groupby('LLLAAGGG'))
Out[101]:
In [102]:
for char, group in itertools.groupby('LLLLAAAGG'):
print(char, '->', list(group))
In [104]:
animals = ['duck', 'eagle', 'rat', 'giraffe', 'bear',
'bat', 'dolphin', 'shark', 'lion']
animals.sort(key=len)
animals
Out[104]:
In [105]:
for length, group in itertools.groupby(animals, len):
print(length, '->', list(group))
In [106]:
# 使用 reverse 生成器从右往左迭代 animals
for length, group in itertools.groupby(reversed(animals), len):
print(length, '->', list(group))
In [107]:
# itertools 产生多个生成器,每个生成器都产出输入的各个元素
list(itertools.tee('abc'))
Out[107]:
In [108]:
g1, g2 = itertools.tee('abc')
next(g1)
Out[108]:
In [109]:
next(g2)
Out[109]:
In [110]:
next(g2)
Out[110]:
In [111]:
list(g1)
Out[111]:
In [112]:
list(g2)
Out[112]:
In [113]:
list(zip(*itertools.tee('ABC')))
Out[113]:
In [115]:
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))
Out[115]:
chain 生成器函数把操作依次交给接收到的各个可迭代对象处理。为此 Python 3.3 引入了新语法,如下:
In [116]:
def chain(*iterables):
for i in iterables:
yield from i # 详细语法在 16 章讲
list(chain(s, t))
Out[116]:
In [118]:
all([1, 2, 3]) # 所有元素为真返回 True
Out[118]:
In [119]:
all([1, 0, 3])
Out[119]:
In [120]:
any([1, 2, 3]) # 有元素为真就返回 True
Out[120]:
In [121]:
any([1, 0, 3])
Out[121]:
In [122]:
any([0, 0, 0])
Out[122]:
In [123]:
any([])
Out[123]:
In [129]:
g = (n for n in [0, 0.0, 7, 8])
any(g)
Out[129]:
In [130]:
next(g) # any 碰到一个为真就不往下判断了
Out[130]:
还有一个内置的函数接受一个可迭代对象,返回不同的值 -- sorted,reversed 是生成器函数,与此不同,sorted 会构建并返回真正的列表,毕竟要读取每一个元素才能排序。它返回的是一个排好序的列表。这里提到 sorted,是因为它可以处理任何可迭代对象
当然,sorted 和这些归约函数只能处理最终会停止的可迭代对象,这些函数会一直收集元素,永远无法返回结果
iter 函数还有一个鲜为人知的用法:传两个参数,使用常规的函数或任何可调用的对象创建迭代器。这样使用时,第一个参数必须是可调用对象,用于不断调用(没有参数),产出各个值,第二个是哨符,是个标记值,当可调用对象返回这个值时候,触发迭代器抛 出 StopIteration 异常,而不产出哨符。
下面是掷骰子,直到掷出 1
In [139]:
from random import randint
def d6():
return randint(1, 6)
d6_iter = iter(d6, 1)
d6_iter
Out[139]:
In [140]:
for roll in d6_iter:
print(roll)
内置函数 iter 的文档有一个实用的例子,逐行读取文件,直到遇到空行或者到达文件末尾为止:
In [143]:
# 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__() 方法只允许客户从生成器中获取数据
这是一项重要的 “改进”,甚至改变了生成器本性,这样使用的话,生成器就变成了协程。所以要提醒一下:
有个简单的生成器函数例子
In [147]:
def f():
x=0
while True:
x += 1
yield x
我们无法通过函数调用抽象产出这个过程,下面似乎能抽象产出这个过程:
In [153]:
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 能 ”解决“ 上面的问题,如下:
In [155]:
def f():
def do_yield(n):
yield n
x = 0
while True:
x += 1
yield from do_yield(x)
In [ ]: