可迭代的对象、迭代器和生成器
理念
迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据
项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)。
看个例子
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,')
print(s)
for world in s:
print(world)
for 循环就是 用到了index, index在__getitem__被用到了
序列可以迭代的原因 iter函数
解释器需要迭代对象 x 时,会自动调用 iter(x)。
内置的 iter 函数有以下作用。
- (1) 检查对象是否实现了 iter 方法,如果实现了就调用它,获取一个迭代器。
- (2) 如果没有实现 iter 方法,但是实现了 getitem 方法,Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素。
- (3) 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C object is not iterable”(C对象不可迭代),其中 C 是目标对象所属的类。
可迭代的对象与迭代器的对比
Python 从可迭代的对象中获取迭代器
标准的迭代器接口有两个方法。
next
返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常。
iter
返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。
abc.Iterator 类
from abc import abstractmethod
class Iterator(Iterable):
__slots__ = ()
@abstractmethod
def __next__(self):
'Return the next item from the iterator. When exhausted, raise StopIteration'
raise StopIteration
def __iter__(self):
return self
@classmethod
def __subclasshook__(cls, C):
if cls is Iterator:
if (any("__next__" in B.__dict__ for B in C.__mro__) and
any("__iter__" in B.__dict__ for B in C.__mro__)):
return True
return NotImplemented
如何使用 next(...) 函数使用迭代器
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,')
# print(s)
#
# for world in s:
# print(world)
## it就是构造后的迭代器
## iter(s3) 就是构建迭代器的可迭代对象。
s3 = Sentence('Pig and Pepper')
it = iter(s3)
print(next(it))
print(next(it))
print(next(it))
# 报错 StopIteration
# print(next(it))
print(list(it))
print(list(iter(s3)))
print(list(iter(s3)))
因为迭代器只需 next 和 iter 两个方法,所以除了调用 next() 方法,以及捕获 StopIteration 异常之外,没有办法检查是否还有遗留的元素。
此外,也没有办法“还原”迭代器。
如果想再次迭代,那就要调用 iter(...),传入之前构建迭代器的可迭代对象。
传入迭代器本身没用,因为前面说过 Iterator.__iter__ 方法的实现方式是
返回实例本身,所以传入迭代器无法还原已经耗尽的迭代器。
典型的迭代器
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 __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return word
def __iter__(self):
return self
s = Sentence('"The time has come," the Walrus said,')
for word in s:
print(word)
❶ 与前一版相比,这里只多了一个 iter 方法。这一版没有 getitem 方法,为
的是明确表明这个类可以迭代,因为实现了 iter 方法。
❷ 根据可迭代协议,__iter__ 方法实例化并返回一个迭代器。
Sentence 类中,__iter__ 方法调用 SentenceIterator 类的构造方法创建一个迭代器并将其返回。
为什么 不写在一起
- 把Sentence变成迭代器:坏主意
构建可迭代的对象和迭代器时经常会出现错误,原因是混淆了二者。
要知道,可迭代的对象有个 iter 方法,每次都实例化一个新的迭代器;
而迭代器要实现 next 方法,返回单个元素,此外还要实现 iter 方法,返回迭代器本身。
因此,迭代器可以迭代,但是可迭代的对象不是迭代器。
除了 iter 方法之外,你可能还想在 Sentence 类中实现 next 方法,让
Sentence 实例既是可迭代的对象,也是自身的迭代器。
可是,这种想法非常糟糕。根据有大量 Python 代码审查经验的 Alex Martelli 所说,这也是常见的反模式。
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:
yield word
return
s = Sentence('"The time has come," the Walrus said,')
for word in s:
print(word)
❸ 这个 return 语句不是必要的;这个函数可以直接“落空”,自动返回。不管有没有
return 语句,生成器函数都不会抛出 StopIteration 异常,而是在生成完全部值之后会直接退出。
❹ 不用再单独定义一个迭代器类!
迭代器其实是生成器对象,每次调用 iter 方法都会自动创建,因为这里的 iter 方法是生成器函数。
生成器函数的工作原理
- 只要 Python 函数的定义体中有 yield 关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂
def gen_123(): # ➊
yield 1 # ➋
yield 2
yield 3
print(gen_123)
print(gen_123())
for i in gen_123(): # ➎
print(i)
g = gen_123() # ➏
print(next(g))
print(next(g))
print(next(g))
## 报错 StopIteration
# print(next(g))
❸ 仔细看,gen_123 是函数对象。
❻ 为了仔细检查,我们把生成器对象赋值给 g。
❽ 生成器函数的定义体执行完毕后,生成器对象会抛出 StopIteration 异常。
把生成器传给next(...) 函数时,生成器函数会向前,执行函数定义体中的下一个 yield 语句,返回产出的值,并在函数定义体的当前位置暂停。
最终,函数的定义体返回时,外层的生成器对象会抛出 StopIteration 异常——这一点与迭代器协议一致。
惰性实现
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()
re.finditer 函数是 re.findall 函数的惰性版本,返回的不是列表,而是一个生成
器,按需生成 re.MatchObject 实例。
如果有很多匹配,re.finditer 函数能节省大量内存。
我们要使用这个函数让第 4 版 Sentence 类变得懒惰,即只在需要时才生成下一个单词。
❶ 不再需要 words 列表。
❷ finditer 函数构建一个迭代器,包含 self.text 中匹配 RE_WORD 的单词,产出
MatchObject 实例。
❸ match.group() 方法从 MatchObject 实例中提取匹配正则表达式的具体文本。
生成器表达式
生成器表达式可以理解为列表推导的惰性版本:不会迫切地构建列表,而是返回一个生成器,按需惰性生成元素。
def gen_AB(): # ➊
print('start')
yield 'A'
print('continue')
yield 'B'
print('end.')
res1 = [x * 3 for x in gen_AB()]
for i in res1: # ➌
print('-->', i)
res2 = (x * 3 for x in gen_AB()) # ➍
print(res2) # ➎
for i in res2: # ➏
print('-->', i)
❷ 列表推导迫切地迭代 gen_AB() 函数生成的生成器对象产出的元素:'A' 和 'B'。注意,下面的输出是 start、continue 和 end.。
❸ 这个 for 循环迭代列表推导生成的 res1 列表。
❹ 把生成器表达式返回的值赋值给 res2。只需调用 gen_AB() 函数,虽然调用时会返回
一个生成器,但是这里并不使用。
❺ res2 是一个生成器对象。
❻ 只有 for 循环迭代 res2 时,gen_AB 函数的定义体才会真正执行。
for 循环每次迭代时会隐式调用 next(res2),前进到 gen_AB 函数中的下一个yield 语句。
注意,gen_AB 函数的输出与 for 循环中 print 函数的输出夹杂在一起。
何时使用生成器表达式
根据我的经验,选择使用哪种句法很容易判断:如果生成器表达式要分成多行写,我倾向
于定义生成器函数,以便提高可读性。此外,生成器函数有名称,因此可以重用。
8 另一个示例:等差数列生成器
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 # ➏
❸ 为了提高可读性,我们创建了 forever 变量,如果 self.end 属性的值是 None那么forever 的值是 True,因此生成的是无穷数列。
❹ 这个循环要么一直执行下去,要么当 result 大于或等于 self.end 时结束。如果循环退出了,那么这个函数也随之退出。
在示例 14-11 中的最后一行,我没有直接使用 self.step 不断地增加 result,
而是选择使用 index 变量,把 self.begin 与 self.step 和 index 的乘积加,计算 result 的各个值,
以此降低处理浮点数时累积效应致错的风险。
标准库里有大量的生成器轮子 page408
深入分析iter函数
可是,iter 函数还有一个鲜为人知的用法:传入两个参数,使用常规的函数或任何可调
用的对象创建迭代器。
这样使用时,第一个参数必须是可调用的对象,用于不断调用(没有参数),产出各个值;
第二个值是哨符,这是个标记值,当可调用的对象返回这个值时,触发迭代器抛出 StopIteration 异常,而不产出哨符。
iter 函数掷骰子,直到掷出 1 点为止
from random import randint
def d6():
return randint(1, 6)
d6_iter = iter(d6, 1)
print(d6_iter)
for roll in d6_iter:
print(roll)
总结
- 迭代器 都有 iter这个方法(函数), (每个都调用__getitem__做兼容) next()到底.
- for每次迭代都是next()
- 对付大文件,大内存. 先返回迭代器对象, 在执行迭代器的具体 操作
- python 用yield 作为生成器的特征
- send可以发送给 那个状态的生成器
- iter别的用法 有两个参数 第二个是哨符 (遇到就停了)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。