可迭代的对象,迭代器和生成器
迭代是数据处理的基石。扫描内存中放不下的数据集时?,我们要找到一种惰性获取数据 项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)。
内存中的数据为啥还是要迭代?
所有生成器都是迭代器,因为生成器完全实现了迭代器接口。不过生成器可以生成数据源外的数据
1 Sentence类第1版:单词序列
# 第一版返回可迭代对象
class Sentence:
def __init__(self, text):
self.text = text
self.words = RE_WORD.findall(text) # 把句子划分为单词序列 re.findall 函数返回一个字符串列表,里面的元素是正则表达式的全部非重叠匹配
# self.words 中保存的是 .findall 函数返回的结果,因此直接返回指定索引位上的单# 词
def __getitem__(self, index):
return self.words[index]
# 实例可以迭代的原因:iter函数 等效 __getitem__ 这里重置了他的返回值
def __len__(self):
return len(self.words)
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
# reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表示形式
s = Sentence('"The time has come," the Walrus said,')
# 验证是否可迭迭代对象
try:
iter(s)
except Exception as e:
print("not iterable")
# 验证是否可迭迭代器
try:
next(iter(s))
# 返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常。
except Exception as e:
print("not Iterator")
序列可以迭代的原因:iter函数
解释器需要迭代对象 x 时,会自动调用 iter(x)。 内置的 iter 函数有以下作用。 (1) 检查对象是否实现了 iter 方法,如果实现了就调用它,获取一个迭代器。 (2) 如果没有实现 iter 方法,但是实现了 getitem 方法,Python 会创建一个迭 代器,尝试按顺序(从索引 0 开始)获取元素。 (3) 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C object is not iterable”(C 对象不可迭代),其中 C 是目标对象所属的类。
任何 Python 序列都可迭代的原因是,它们都实现了 getitem 方法,标准的序列也都实现了 iter 方法
2 可迭代的对象与迭代器的对比
明确可迭代的对象和迭代器之间的关系:Python 从可迭代的对象中获取迭代器。
迭代器 可使用next方法
3 Sentence类第2版:典型的迭代器
# 第二版返回迭代器
class Sentence2:
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 __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self
构建可迭代的对象和迭代器时经常会出现错误,原因是混淆了二者。要知道,可迭代的对 象有个 iter 方法,每次都实例化一个新的迭代器;而迭代器要实现 next 方 法,返回单个元素,此外还要实现 iter 方法,返回迭代器本身。 因此,迭代器可以迭代,但是可迭代的对象不是迭代器。
迭代器模式可用来: 访问一个聚合对象的内容而无需暴露它的内部表示 支持对聚合对象的多种遍历 为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代) 为了“支持多种遍历”,必须能从同一个可迭代的实例中获取多个独立的迭代器,而且各个 迭代器要能维护自身的内部状态,因此这一模式正确的实现方式是,每次调用 iter(my_iterable) 都新建一个独立的迭代器。这就是为什么这个示例需要定义 SentenceIterator 类。
且迭代器没有len 方法
s = 'ABC'
>>> for char in s:
... print(char)
s = 'ABC'
>>> it = iter(s) # ➊
>>> while True:
... try:
... print(next(it)) # ➋
... except StopIteration: # ➌
... del it # ➍
... break #
所以for 的本质是while 语法糖
何使用
iter(...) 函数构建迭代器,以及如何使用 next(...) 函数使用迭代器:
4 Sentence类第3版:生成器函数
实现相同功能,但却符合 Python 习惯的方式是,用生成器函数代替 SentenceIterator 类
class Sentence3:
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 # python 生成器关键字yield 作用等于SentenceIterator
return
1 只要 Python 函数的定义体中有 yield 关键字,该函数就是生成器函数。调用生成器函数 时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。
2 生成器函数的定义体中通常都有循环,不过这不是最核心的条件;
3 生成器是迭代器,会生成传给 yield 关键字的表达式的值。
4 函数返回值;调用生成器函数返回生成器;生成器产出或生成值
5 for 循环中 会隐式调用 next() 函数
5 Sentence 类第4版 :惰性实现
设计 Iterator 接口时考虑到了惰性:next(my_iterator) 一次生成一个元素。懒惰的 反义词是急迫,其实,惰性求值(lazy evaluation)和及早求值(eager evaluation)是编程 语言理论方面的技术术语。
在生成器函数中调用 re.finditer 生成器函数,实现Sentence 类
class Sentence4: # 此时实例为一个生成器对象了 调用生成器对象需要继续使用for 触发yield 实现惰性生产数据
def __init__(self, text):
self.text = text # 不再需要 words 列表。
def __repr__(self):
return 'Sentence(%s)' % reprlib.repr(self.text)
def __iter__(self):
for match in RE_WORD.finditer(self.text):
yield match.group() # match.group() 方法从 MatchObject 实例中提取匹配正则表达式的具体文本。
s = Sentence4('"The time has come," the Walrus said,')
print(s)
for i in s:
print(i)
6 Sentence类第5版:生成器表达式
生成器表达式可以理解为列表推导的惰性版本:
不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。
也就是说,如果列表推导是制造列表的工厂,那么生成器表达式就是制造生成器的工厂。
def gen_AB():
print('start')
yield 'A'
print('continue')
yield 'B'
print('end.')
res1 = [x * 3 for x in gen_AB()] # 列表推导式
print(res1)
for i in res1:
print('-->', i)
res2 = (x * 3 for x in gen_AB()) # 生成器表达式 res 2 为一个生成器对象 只有for 时才能迭代出值
print(res2)
for i in res2:
print('-->', i)
# 已经和asyc 很像了
sentence_genexp.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))
'''
唯一的区别是 __iter__ 方法,这里不是生成器函数了(没有 yield),而
是使用生成器表达式构建生成器,然后将其返回。不过,最终的效果一样:调用
__iter__ 方法会得到一个生成器对象。
生成器表达式是语法糖:完全可以替换成生成器函数,不过有时使用生成器表达式更便
利。
'''
何时使用生成器表达式
如果生成器表达式要分成多行写,定义生成器函数,以便提高可读性。此外,生成器函数有名称,因此可以重用。其他时候用生成器表达式
如果函数或构造方法只有一个参数,传入生成器表达式时不用写一对调用函数的括 号,再写一对括号围住生成器表达式,只写一对括号就行了,如示例 10-16 中 mul 方法对 Vector 构造方法的调用,转摘如下。然而,如果生成器表达式后面 还有其他参数,那么必须使用括号围住,否则会抛出 SyntaxError 异常:
def __mul__(self, scalar):
if isinstance(scalar, numbers.Real):
return Vector(n * scalar for n in self)
else:
return NotImplemented
目前所见的 Sentence 类示例说明了如何把生成器当作典型的迭代器使用,即从集合中获 取元素。不过,生成器也可用于生成不受数据源限制的值
8 另一个示例:等差数列生成器典
典型的迭代器模式作用很简单——遍历数据结构。不过,即便不是从集合中获取元素,而 是获取序列中即时生成的下一个值时,也用得到这种基于方法的标准接口。例如,内置的 range 函数用于生成有穷整数等差数列(Arithmetic Progression, AP),itertools.count 函数用于生成无穷等差数列。
9 标准库中的生成器函数
标准库提供了很多生成器,有用于逐行迭代纯文本文件的对象,还有出色的 os.walk 函 数
14 把生成器当成协程
与 .next() 方法一样,.send() 方法致使生成器前进到下一个 yield 语句。不 过,.send() 方法还允许使用生成器的客户把数据发给自己,即不管传给 .send() 方法 什么参数,那个参数都会成为生成器函数定义体中对应的 yield 表达式的值。也就是 说,.send() 方法允许在客户代码和生成器之间双向交换数据。而 .next() 方法只 允许客户从生成器中获取数据。
这是一项重要的“改进”,甚至改变了生成器的本性
生成器用于生成供迭代的数据 协程是数据的消费者 为了避免脑袋炸裂,不能把这两个概念混为一谈 协程与迭代无关 注意,虽然在协程中会使用 yield 产出值,但这与迭代无关