《Python基础教程(第3版)》笔记:9.6迭代器和9.7生成器
可迭代任何实现了__iter__方法的对象,方法__iter__返回一个迭代器,它是包含方法__next__的对象;若迭代器没有可返回的值,将引发StopIteration异常。
注意 实现了方法__iter__的对象是可迭代的,而实现了方法__next__的对象是迭代器。
# 斐波那契数列的迭代器
class Fibs:
def __init__(self):
self.a = 0
self.b = 1
def __next__(self):
self.a, self.b = self.b, self.a+self.b
return self.a
def __iter__(self):
return self
fibs = Fibs()
for f in fibs:
if f>1000:
print(f)
break
提示 通过对可迭代对象调用内置函数iter,可获得一个迭代器。
>>> it = iter([1,2,3])
>>> next(it)
1
>>> it
<list_iterator object at 0x00000188C71CCA30>
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
next(it)
StopIteration
9.6.2 从迭代器创建序列
-
使用构造函数list显式地将迭代器转换为列表
class TestIterator: value = 0 def __next__(self): self.value +=1 if self.value > 10: raise StopIteration return self.value def __iter__(self): return self ti = testiterator() print(list(ti)) #[1,2,3,4,5,6,7,8,9,10]
9.7 生成器
生成器是一种使用普通函数语法定义的迭代器。
def flatten(nested): for sublist in nested: for element in sublist: yield element nested = [[1,2], [3,4],[5]] print(list(flatten(nested)))
包含yield语句的函数都被称为生成器。每次使用yield生成一个值后,函数都将冻结,即:“在此停止执行 ,等待被重新唤醒”
生成器表达式
>>> g = ((i+2)**2 for i in range(2, 27))
>>> next(g)
9.7.2 递归式生成器
如果要处理任意层嵌套的列表
def flatten(nested):
try:
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
调用flatten时,有两种可能性:基线条件和递归条件。在基线条件下,要求这个函数展开单个元素(如一个数)。在这种情况下,for循环将引发TypeError异常(因为你试图迭代一个数),而这个生成器只生成一个元素。
然而,如果要展开的是一个列表(或其他任何可迭代对象),你就需要做:**遍历所有的子列表(其中有些可能并不是列表)并对它们调用flatten,然后使用另一个for循环生成展开后的子列表中的所有元素。这可能看起来有点不可思议,但确实可行。
>>> list(flatten([[[1], 2], 3, 4, [5, [6, 7]], 8]))
[1, 2, 3, 4, 5, 6, 7, 8]
然而,这个解决方案存在一个问题。如果nested是字符串或类似于字符串的对象,它就属于序列,因此不会引发TypeError异常,可你并不想对其进行迭代。
注意 在函数flatten中,不应该对类似于字符串的对象进行迭代,主要原因有两个。首先,你想将类似于字符串的对象视为原子值,而不是应该展开的序列。其次,对这样的对象进行迭代会导致无穷递归,因为字符串的第一个元素是一个长度为1的字符串,而长度为1的字符串的第一个元素是字符串本身!
要处理这种问题,必须在生成器开头进行检查。要检查对象是否类似于字符串,最简单、最快捷的方式是,尝试将对象与一个字符串拼接起来,并检查这是否会引发TypeError异常①。添加这种检查后的生成器如下:
def flatten(nested):
try:
# 不迭代类似于字符串的对象:
try: nested + ''
except TypeError: pass
else: raise TypeError
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
如你所见,如果表达式nested + ''引发了TypeError异常,就忽略这种异常;如果没有引发TypeError异常,内部try语句中的else子句将引发TypeError异常,这样将在外部的excpet子句中原封不动地生成类似于字符串的对象。明白了吗?
9.7.3 通用生成器
生成器是包含关键字yield的函数,但被调用时不会执行函数体内的代码,而是返回一个迭代器。
生成器由两个单独的部分组成:生成器的函数和生成器的迭代器。
- 生成器的函数是由def语句定义的,其中包含yield
- 生成器的迭代器是这个函数返回的结果
注意观察下列代码的反馈:
>>> def simple_generator():
yield 1
...
>>> simple_generator
<function simple_generator at 153b44>
>>> simple_generator()
<generator object at 1510b0>
9.7.4 生成器的方法
在生成器开始运行后,可使用生成器和外部之间的通信渠道向它提供值。这个通信渠道包含如下两个端点。
-
外部世界:外部世界可访问生成器的方法send,接受一个参数(要发送的“消息”,可以是任何对象)。
-
生成器:在挂起的生成器内部,yield可能用作表达式而不是语句。换而言之,当生成器重新运行时,yield返回一个值——通过send从外部世界发送的值。如果使用的是next,yield将返回None
请注意,仅当生成器被挂起(即遇到第一个yield)后,使用send(而不是next)才有意义。
要在此之前向生成器提供信息,可使用生成器的函数的参数。
def repeater(value):
while True:
new = (yield value)
if new is not None: value = new
>>> r = repeater(42)
>>> next(r)
42
>>> next(r)
42
>>> r.send("Hello, world!")
"Hello, world!"
>>> next(r)
"Hello, world!"
>>> next(r)
"Hello, world!"
注意 使用圆括号将yield表达式括起来了。并非必须这样做,但小心驶得万年船。如果要以某种方式使用返回值,就不管三七二十一,将其用圆括号括起吧。