在Python中,所有集合都可以爹迭代。在Python语言内部,迭代器用于支持:
- for循环
- 构建和扩展集合类型
- 逐行遍历文本文件
- 列表推导、字典推导和集合推导
- 元组拆包
- 调用函数时,使用*拆包实参
14.1 Sentence第一版:单词序列
我们要实现一个Sentence类,以此打开探索可迭代对象的旅程。我们向这个类的构造方法传入包含一些文本的字符串,然后可以逐个单词迭代。
示例14-1 sentence.py:把句子划分为单词序列
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)
示例14-2 测试Sentence实例能否迭代
from sentence import Sentence
s = Sentence('"The time has come," the Walrus said,')
s
Sentence('"The time ha... Walrus said,')
for word in s:
print(word)
The
time
has
come
the
Walrus
said
序列可以迭代的原因:iter函数 解释器需要迭代对象x时,会自动调用iter(x)
内置的iter函数有以下作用。
- 检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器。
- 如果没有实现__iter__方法,但是实现了__getitem__方法,Python会创建一个迭代器,尝试按顺序获取元素。
- 如果尝试失败,Python会抛出TypeError异常,通常会提示:“C object is not iterable”,其中C是目标对象所属的类。
任何Python序列都可迭代的原因是,它们都实现了__getitem__方法。
14.2 可迭代的对象与迭代器的对比
可迭代的对象和迭代器之间的关系:Python从可迭代的对象中获取迭代器。
for循环,迭代一个字符串。
>>> for char in s :
... print(char)
...
A
B
C
如果没有for语句,不得不使用while循环模拟,要像下面这样写:
>>> s = "ABC"
>>> it = iter(s)
>>> while True:
... try:
... print(next(it))
... except StopIteration:
... del it
... break
...
A
B
C
标准的迭代器接口有两个方法:
__next__
返回下一个可用的元素,如果没有元素了,抛出StopIteration异常。
__iter__
返回self,一遍在英国使用可迭代对象的地方使用迭代器,例如在for循环中。
图14-1:Iterable和Iterator抽象基类
迭代Sentece类
s3 = Sentence('Pig and Pepper')
it = iter(s3)
it
<iterator object at 0x7fda48143828>
next(it)
'Pig'
next(it)
'and'
next(it)
'Pepper'
next(it)
Traceback (most recent call last):
File "<input>", line 1, in <module>
StopIteration
list(it)
[]
list(iter(s3))
['Pig', 'and', 'Pepper']
迭代器
迭代器是这样的对象,实现了无参数的__next__方法,返回序列中的下一个元素;如果没有元素了,那么抛出StopIteration异常。Python中的迭代器还实现了__iter__方法,因此迭代器也可以迭代。
14.3 Sentence类第2版:典型的迭代器
第2版Sentence根据《设计模式》艺术给出的模型,实现了典型的迭代器设计模式。注意,这不符合Python的习惯做法。
之所以这么做,是为了清楚地说明可迭代的对象和迭代器之间的重要区别,以及二者之间的联系。
示例14-4 sentence_iter.py:使用迭代器模式实现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):
return SentenceIterator(self.words)
class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration
self.index += 1
return word
把Sentence变成迭代器:坏主意
构建可迭代的对象和迭代器时经常会出现错误,原因是混淆了二者。要知道,可迭代的对象有个__iter__方法,每次都实例化一个新的迭代器;而迭代器要实现__next__方法,返回单个元素,此外还要实现__iter__方法,返回迭代器本身。
14.4 Sentence类第三版:生成器函数
实现相同的功能,但却符合Python习惯的方式是,用生成器函数代替SentenceIterator类。
示例14-5 sentence_gen.py:使用生成器函数实现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 __iter__(self):
for word in self.words:
yield word
return
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
s3 = Sentence("Pig and Pepper")
it = iter(s3)
print(type(it))
print(next(it))
print(next(it))
print(next(it))
<class 'generator'>
Pig
and
Pepper
生成器的工作原理 只要Python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。
下面以一个简单的函数说明生成器的行为:
>>> def gen_123():
... yield 1
... yield 2
... yield 3
...
>>> gen_123
<function gen_123 at 0x000002390082C268>
>>> gen_123()
<generator object gen_123 at 0x0000023900851DE0>
>>> for i in gen_123():
... print(i)
...
1
2
3
>>> g = gen_123()
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
Traceback (most recent call last):
...
StopIteration
示例14-6 运行时打印消息的生成器函数
>>> def gen_AB():
... print('start')
... yield 'A'
... print('continue')
... yield('B')
... print('end')
...
>>> for c in gen_AB():
... print('-->', c)
...
start
--> A
continue
--> B
end
14.5 Sentence类第4版:惰性实现
设计Iterator接口时考虑到了惰性:next(my_iterator)一次生成一个元素。
目前实现的几版Sentence类都不具有惰性,因为__init__方法急迫地构建好了文本中的单词列表,然后将其绑定到self.words属性上。
示例14-7 sentence_gen2.py:在生成器函数中调用re.finditer生成器函数
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()
14.6 Sentence类第5版:生成器表达式
生成器表达式可以理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。
示例14-8 对比列表推导与生成器表达式
>>> def gen_AB():
... print('start')
... yield 'A'
... print('continue')
... yield 'B'
... print('end')
...
>>> res1 = [x * 3 for x in gen_AB()]
start
continue
end
>>> res1
['AAA', 'BBB']
>>> res2 = (x*3 for x in gen_AB())
>>> res2
<generator object <genexpr> at 0x0000023900B44570>
>>> for i in res2:
... print('-->', i)
...
start
--> AAA
continue
--> BBB
end
示例14-9 sentence_genxp.py:使用生成器表达式实现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))
生成器表达式是语法糖:完全可以替换生成器函数。
14.7 何时使用生成器表达式
如果生成器表达式要分成多行写,倾向定义生成器函数,以便提高可读性。此外,生成器函数有名称,因此可以重用。