关注的本节内容:
Sentence
类调用iter(x)
, x
为可迭代对象。按照三个顺序:
1. 是否实现了__item__
方法,如果实现了,调用他并获取一个迭代器。
2. 是否实现了__getitem__
方法,基于鸭子类型,这是为了向后兼容。
3. 抛出异常TypeError
显然,从鸭子类型duck type
来说,标准的可迭代对象有__getitem__
和__item__
方法,所以实现一个可迭代对象也应该实现这两个抽象方法。
从白鹅类型goose type
来说,考虑继承,实现__item__
抽象方法即可。
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): # 实现__len__不是迭代必须的
return len(self.words)
def __repr__(self):
return 'Sentence(%s))' % reprlib.repr(self.text)
Python 3.4 开始,检查对象 x 能否迭代,最准确的方法是:
调用 iter(x)
函数,如果不可迭代,再处理 TypeError 异常。这
比使用 isinstance(x, abc.Iterable)
更准确,因为 iter(x)
函数会考虑到遗留的 __getitem__
方法,而 abc.Iterable
类则不考虑。
迭代器是这样的对象:实现了无参数的 __next__
方法,返回序列中的下一个元素;如果没有元素了,那么抛出StopIteration
异常。Python 中的迭代器还实现了 __iter__
方法,因此迭代器也可以迭代。
迭代器实现两个接口:
__next__
__iter__
方法实例化,并返回一个迭代器迭代器的抽象基类是 collectons.abc.Iterator
,和 collections.abc.Iterable
__next__
抽象方法定义在Iterator
中,__iter__
方法定义在Iterable
中,Iterable
继承了Iterator
。
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text)
def __iter__(self):
return SentenceIterator(self.text)
def __repr__(self):
return 'Sentence(%s))' % reprlib.repr(self.text)
class SentenceIterator:
def __init__(self, words):
self.words = words
self.index = 0
def __next__(self):
try:
word = self.words[index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self
首先,需要确定这个典型的迭代器实现了哪些内容,符合了哪些标准?
__iter__
方法返回了一个实例化的迭代器。后面讲到为什么要建一个新的SentenceIterator
SentenceIterator
同时实现了__iter__
和__next__
。代码中也可以看出来,实现__iter__
其实是不必要的,但他可以帮助通过迭代器的测试,包括isinstance(SentenceIterator,collectons.abc.IIterator)
。Sentence
中实现__next__
这里可以回答上文中的为什么要有SentenceIterator
的问题。
设计模式中(没看过)谈到迭代器模式:
其中,对聚合对象的多种遍历的含义就是,每次遍历都是相互独立,不受干扰的,结合 1
,那么建一个新的SentenceIterator
是最好的办法。
关键字yield
。函数内只要有yield
,那么这个函数就是生成器函数。这个在讲生成器的技巧随处可见。
一般函数内会带个gen
表明是生成器。
def gen_123():
for i in range(5):
yield 2*i
函数即对象
gen_123
是一个function
gen_123()
是一个generator
改进代码,代替 SentenceIterator
类:
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)
Sentence
类(利用现有迭代器)惰性即lazy
,他的反义词是急迫 eager
分为惰性求值 lazy evaluation
和及早求值eager evaluation
。
所谓惰性求值,就是让__next__
一次只生成一个,这才符合我们想要利用迭代器的根本目的——节约内存,而前面的版本在__init__
的时候实际上已经生成全部数据。
书上指出
def __init__(self, text):
self.text = text
def __iter__(self):
for match in RE_WORD.finditer(self.text):
yield match.group()
利用re
库中的迭代器,
实际上暗示了要利用其他现有的迭代器来优化。
实际上是一种语法糖,下面就是本节Sentence
的最终版本:
class Sentence:
def __init__(self, text):
self.text = text
def __iter__(self):
return (match.group() for match in RE_WORD.finditer(self.text))
def __repr__(self):
return 'Sentence(%s))' % reprlib.repr(self.text)
要分清可迭代对象和迭代器,迭代器是超集。标准迭代器必须要实现__iter__
和__next__
,__iter__
返回的是一个迭代器的实例。学会使用yield
和生成器表达式
来更像Python
。
测试的时候发现一个有趣的地方:
gen=(x*3 for x in range(5))
>>> gen
at 0x7ff495a3d830>
>>> next(gen)
0
>>> next(gen)
3
>>> next(gen)
6
>>> list(gen)
[9, 12]
>>> next(gen)
Traceback (most recent call last):
File "" , line 1, in
next(gen)
StopIteration
实际上list的构造方法
就是调用了迭代器,gen在构造的时候已经用的最后了。如果想要重复用,首先就应该
gen=(x*3 for x in range(5))
n=gen
开始调用next(n),gen作`原始数据`