序列可迭代的原因
我们来构造一个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)
>>> s = Sentence('"The time has come," the Walrus said,')
>>> s
Sentence('"The time ha... Walrus said,')
# s可以迭代,因为实现了__getitem__方法
>>> for word in s:
... print(word)
The
time
has
come
the
Walrus
said
# 因为可以迭代,所以Sentence 对象可以用于构建列表和其他可迭代的类型
>>> list(s)
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']
序列可迭代的原因:iter函数,解释器需要迭代对象x时,会自动调用iter(x), 内置的iter有一下作用:
- 检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器。
- 如果没有实现__iter__ 方法,但是实现了__getitem__ 方法,Python 会创建一个迭代器,尝试按顺序(从索引0 开始)获取元素。
- 如果尝试失败,Python 抛出TypeError 异常,通常会提示“C object is not iterable”(C对象不可迭代),其中C 是目标对象所属的类
可迭代的对象与迭代器对比
使用iter内置函数可以获取可迭代对象的迭代器
- 如果对象实现了能返回迭代器的__iter__方法,那对象就是可迭代的
- 序列都可以迭代;实现了__getitem__方法,而且其参数是从0开始索引,这种对象也可以迭代
- 我们要明确可迭代对象和迭代器之间的关系,python从可迭代对象中获取迭代器
>>> S = "ABC"
# 使用可迭代对象构建迭代器it
>>> it = iter(s)
>>> it
<str_iterator at 0x7f08cc460e10>
>>> while True:
... try:
... print(next(it))
... except StopIteration:
... del it
... break
A
B
C
标准迭代器接口的两个方法:
- __next__:返回下一个可用的元素,如果没有元素了,抛出StopIteration 异常
- __iter__:返回self,以便在应该使用可迭代对象的地方使用迭代器
import collections
# 检查对象是否为迭代器
>>> isinstance(iter('ABC'), collections.abc.Iterator)
True
>>> s3 = Sentence('Pig and Pepper')
>>> it = iter(s3)
>>> it
<iterator object at 0x...>
>>> next(it)
'Pig'
>>> next(it)
'and'
>>> next(it)
'Pepper'
# 没有单词了,抛出StopIteration异常
>>> next(it)
Traceback (most recent call last):
...
StopIteration
# 到头后,迭代器没用了
>>> list(it)
[]
# 如果想再次迭代,要重构迭代器
>>> list(iter(s3))
['Pig', 'and', 'Pepper']
典型的迭代器
我们自己写一个类来实现迭代器
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)
# 为了表明这个类可以迭代,实现了__iter__方法
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__ 这个具体方法.
构建可迭代的对象和迭代器经常会出现错误,原因是混淆了两者:
- 可迭代的对象有个__iter__ 方法,每次都实例化一个新的迭代器
- 而迭代器要实现__next__ 方法,返回单个元素,此外还要实现__iter__ 方法,返回迭代器本身。
- 迭代器可以迭代,但是可迭代的对象不是迭代器
- 可迭代的对象一定不能是自身的迭代器。也就是说,可迭代的对象必须实现
__iter__ 方法,但不能实现__next__ 方法
生成器函数
我们可以用生成器函数代替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
在上例中的__iter__函数中,
- 这个return语句不是必要的;这个函数可以直接“落空”,自动返回。不管有没有return 语句,生成器函数都不会抛出StopIteration 异常,而是在生成完全部值之后会直接退出。
- __iter__方法是生成器函数,调用是会构建一个实现了迭代器接口的生成器对象
生成器函数的工作原理
只要Python 函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。
>>> def gen_123(): # 只要函数包含yield关键字,就是生成器函数
... yield 1
... yield 2
... yield 3
...
>>> gen_123 # 函数对象
<function gen_123 at 0x...>
>>> gen_123() # 调用函数,返回生成器对象
<generator object gen_123 at 0x...>
>>> for i in gen_123(): # 生成器也是迭代器
... print(i)
1
2
3
>>> g = gen_123()
>>> next(g) # 因为g是迭代器,可以调用next(g)获取下个元素
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
Traceback (most recent call last):
...
StopIteration
生成器也是迭代器,可以调用next方法获取下一个元素
生成器函数定义体执行过程
>>> 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.
>>>
Sentence类的惰性实现
设计Iterator接口时考虑了惰性:next(my_iterator)一次生成一个元素。之前实现的Sentence类中都不具有惰性,因为__init__方法构建好了单词列表,绑定到self.words属性上,很耗内存,我们将类改进如下:
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence:
def __init__(self, text):
# 不再需要words列表
self.text = text
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
''' finditer 函数构建一个迭代器,包含self.text
中匹配RE_WORD 的单词,产出MatchObject实例'''
for match in RE_WORD.finditer(self.text):
yield match.group()
生成器表达式
生成器表达式可以理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。也就是说,如果列表推导是制造列表的工厂,那么生成器表达式就是制造生成器的工厂
>>> def gen_AB(): #
... print('start')
... yield 'A'
... print('continue')
... yield 'B'
... print('end.')
...
# 列表推导急切的迭代gen_AB()函数
>>> res1 = [x*3 for x in gen_AB()]
start
continue
end.
>>> for i in res1:
... print('-->', i)
--> AAA
--> BBB
# 生成器表达式,返回一个生成器对象,但是这里并不使用
>>> res2 = (x*3 for x in gen_AB())
>>> res2
<generator object <genexpr> at 0x10063c240>
"""
只有for讯号迭代res2时,gen_AB()函数的定义体才会真正执行
for 循环每次迭代时会隐式调用next(res2),前进到gen_AB 函数
中的下一个yield 语句。注意,gen_AB 函数的输出与for 循环中
print 函数的输出夹杂在一起。
"""
>>> for i in res2:
... print('-->', i)
...
start
--> AAA
continue
--> BBB
end.
关于何时使用生成器表达式:
如果生成器表达式要分成多行写,倾向于定义生成器函数,以便提高可读性。此外,生成器函数有名称,因此可以重用
等差数列生成器
我们定义一个等差数列生成器ArithmeticProgression类来说明如何使用生成器函数实现__iter__方法
class ArithmeticProgression:
def __init__(self, begin, step, end=None):
self.begin = begin
self.step = step
self.end = end # None # 无穷数列
def __iter__(self):
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)
[0, 1, 2]
>>> ap = ArithmeticProgression(1, .5, 3)
>>> list(ap)
[1.0, 1.5, 2.0, 2.5]
>>> ap = ArithmeticProgression(0, 1/3, 1)
>>> list(ap)
[0.0, 0.3333333333333333, 0.6666666666666666]
# 使用itertools生成等差数列
>>> import itertools
>>> gen = itertools.count(1, .5)
>>> next(gen)
1
>>> next(gen)
1.5
>>> next(gen)
2.0
>>> next(gen)
2.5
可迭代归约函数
可迭代归约函数接受一个可迭代对象,然后返回单个结果。典型的归约函数如下:
- all(it): it中所有元素为真是返回True,否则返回False,all([])返回True
- any(it): 只要it中有元素为真值就返回True,否则返回False, any([])返回False
- min(it)
- reducr(func, it)
- sum(it)
# g is a generator
>>> g = (n for n in [0,5,2,1])
>>>any(g)
True
对于any和all函数来说,有一个重要的优化措施是reduce做不到了,这两个函数会短路(即一旦确定结果就立即停止使用迭代器)。
iter 函数还有一个鲜为人知的用法:传入两个参数,使用常规的函数或任何可调用的对象创建迭代器。这样使用时,第一个参数必须是可调用的对象,用于不断调用(没有参数),产出各个值;第二个值是哨符,这是个标记值,当可调用的对象返回这个值时,触发迭代器抛出StopIteration 异常,而不产出哨符。
下述示例展示如何使用iter 函数掷骰子,直到掷出1 点为止
>>> def d6():
... return randint(1, 6)
...
>>> d6_iter = iter(d6, 1)
>>> d6_iter
<callable_iterator object at 0x00000000029BE6A0>
>>> for roll in d6_iter:
... print(roll)
...
4
3
6
3
一个常见的用法是用于文件读取,这段代码逐行读取文件,直到遇到空行或者到达文件末尾为止
with open('mydata.txt') as fp:
for line in iter(fp.readline, '\n'):
process_line(line)