目录
①实现一个Sentence,以此开始探索可迭代对象。第一版要实现序列协议。
可迭代的对象、迭代器和生成器
所有生成器都是迭代器,生成器完全实现了迭代器接口。迭代器从集合中取出元素,生成器用于’凭空‘生成元素。
生成器有广泛的用途,例如内置的range()函数返回一个类似于生成器的对象,而以前返回完整的列表。
在 Python 中, 所有集合都可以迭代。 在 Python 语言内部, 迭代器用于支持:
- for 循环
- 构建和扩展集合类型
- 逐行遍历文本文件
- 列表推导、 字典推导和集合推导
- 元组拆包
- 调用函数时, 使用 * 拆包实参
一、Sentence类第一版:单词序列
①实现一个Sentence,以此开始探索可迭代对象。第一版要实现序列协议。
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] # 返回指定索引位上的单词
def __len__(self): # 为了完善序列协议,实现了这个方法。单纯为了实现序列,这个方法没有必要
return len(self.words)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text) # 这个实用函数用于生成大型数据结构的简略字符串表示形式
②测试Sentence是否可以迭代
s = Sentence('"The time has come," the Walrus said,')
print(s) #输出结果是reprlib.repr方法生成的
for i in s:
print(i)
Sentence实例可以迭代。
③Sentence实例可以迭代的原因:
- 检查对象是否实现了 __iter__ 方法, 如果实现了就调用它, 获取一个迭代器。
- 如果没有实现 __iter__ 方法, 但是实现了 __getitem__ 方法,Python 会创建一个迭代器, 尝试按顺序(从索引 0 开始) 获取元素。
- 如果尝试失败, Python 抛出 TypeError 异常, 通常会提示“C objectis not iterable”(C 对象不可迭代) , 其中 C 是目标对象所属的类。
任何 Python 序列都可迭代的原因是, 它们都实现了 __getitem__ 方法。 其实, 标准的序列也都实现了 __iter__ 方法。
二、可迭代的对象与迭代器的对比
①可迭代的对象
使用 iter 内置函数可以获取迭代器的对象。 如果对象实现了能返回迭代器的 __iter__ 方法, 那么对象就是可迭代的。 序列都可以迭代; 实现了 __getitem__ 方法, 而且其参数是从零开始的索引, 这种对象也可以迭代。
可迭代的对象和迭代器之间的关系: Python 从可迭代的对象中获取迭代器。
②标准的迭代器接口有两个方法。
__next__
返回下一个可用的元素, 如果没有元素了, 抛出 StopIteration异常。
__iter__
返回 self, 以便在应该使用可迭代对象的地方使用迭代器, 例如在 for 循环中。
③迭代器
迭代器是这样的对象: 实现了无参数的 __next__ 方法, 返回序列中的下一个元素; 如果没有元素了, 那么抛出 StopIteration 异常。Python 中的迭代器还实现了 __iter__ 方法, 因此迭代器也可以迭代。
三、Sentence类第二版:典型的迭代器
①
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): # 多了一个__iter__方法
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] #获取self.index索引位上的单词
except IndexError:
raise StopIteration() #索引完毕,抛出错误
self.index += 1
return word
def __iter__(self):
return self
②把Sentence变成迭代器是一个坏主意
可迭代的对象有个 __iter__ 方法, 每次都实例化一个新的迭代器; 而迭代器要实现 __next__ 方法, 返回单个元素, 此外还要实现__iter__ 方法, 返回迭代器本身。
可迭代的对象一定不能是自身的迭代器。 也就是说, 可迭代的对象必须实现 __iter__ 方法, 但不能实现 __next__ 方法。另一方面, 迭代器应该一直可以迭代。 迭代器的 __iter__ 方法应该返回自身。
四、Sentence类第三版:生成器函数
与上一版实现功能相同,但却更符合Python的习惯。使用生成器函数。
①
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: # 迭代self.words
yield word # 产出当前word
return
②生成器函数的工作原理
只要Python函数中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。
普通的函数与生成器函数在句法上唯一的区别是, 在后者的定义体中有 yield 关键字。
>>>def gen():
yield 1
yield 2
yield 3
>>>g=gen() #把 生成器对象赋值给g,g是迭代器
>>>next(g)
1
>>>next(g)
2
>>>next(g)
3
>>>next(g)
Traceback (most recent call last):
File "<input>", line 1, in <module>
StopIteration
>>>for i in gen(): #生成器是迭代器,会生成传给yield关键字的表达式的值。
print(i)
1
2
3
生成器函数会创建一个生成器对象, 包装生成器函数的定义体。 把生成器传给 next(...) 函数时, 生成器函数会向前, 执行函数定义体中的下一个 yield 语句, 返回产出的值, 并在函数定义体的当前位置暂停。 最终, 函数的定义体返回时, 外层的生成器对象会抛出StopIteration 异常。
可以这个理解:遇到yield语句,返回产出的值,下一次调用next()后,从上次退出的地方接着执行。
def gen_AB():
print('start')
yield 'A'
print('continue')
yield 'B'
print('end.')
for c in gen_AB():
print('-->', c)
上述执行顺序:
五、把生成器当做协程
与 .__next__() 方法一样, .send() 方法致使生成器前进到下一个yield 语句。 不过, .send() 方法还允许使用生成器的客户把数据发给自己, 即不管传给 .send() 方法什么参数, 那个参数都会成为生成器函数定义体中对应的 yield 表达式的值。 也就是说, .send() 方法允许在客户代码和生成器之间双向交换数据。 而 .__next__() 方法只允许客户从生成器中获取数据。
这是一项重要的“改进”, 甚至改变了生成器的本性: 像这样使用的话,生成器就变身为协程。