迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)。
所有生成器都是迭代器,因为生成器完全实现了迭代器接口。迭代器用于从集合中取出元素;而生成器用于“凭空”生成元素。
我们要实现一个 Sentence 类,以此打开探索可迭代对象的旅程。我们向这个类的构造方法传入包含一些文本的字符串,然后可以逐个单词迭代。
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence(object):
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('this time has come, we are ready')
print(s)
for i in s:
print(i)
解释器需要迭代对象 x 时,会自动调用 iter(x)。内置的 iter 函数有以下作用。
1. 检查对象是否实现了iter方法,如果实现了就调用它,获取一个迭代器。
2. 如果没有实现iter方法,但是实现了getitem方法, Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素。
3. 如果尝试失败, Python 抛出 TypeError 异常,通常会提示“C object is not iterable”。
==使用 iter()内置函数可以获取迭代器的对象==。可迭代的对象和迭代器之间的关系: Python 从可迭代的对象中获取迭代器。
next返回下一个可用的元素,如果没有元素了,==抛出 StopIteration 异常==。
iter返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。
s = Sentence('this time has come, we are ready')
it = iter(s)
print(next(it))
print(next(it))
可以在Sentence类中实现iter方法,并且返回一个迭代器SentenceIterator实例。而SentenceIterator类必须是一个迭代器,迭代器应该实现next和iter两个方法并且迭代器的iter需要返回self。
总结:
1. 可迭代的对象(Sentence的实例)一定不能是自身的迭代器。也就是说,可迭代的对象必须实现iter方法,但不能实现next方法。
2. 迭代器(SentenceIterator)应该一直可以迭代。迭代器的iter方法应该返回自身。
实现相同功能,但却符合 Python 习惯的方式是,用生成器函数代替 SentenceIterator 类。
import re
import reprlib
RE_WORD = re.compile('\w+')
class Sentence(object):
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
s = Sentence('this time has come, we are ready')
it = iter(s)
print(next(it))
print(next(it))
Sentence.iter方法的作用了:iter方法是生成器函数,调用时会构建一个实现了迭代器接口的生成器对象,因此不用再定义SentenceIterator 类了。
==只要 Python 函数的定义体中有 yield 关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象==。也就是说,生成器函数是生成器工厂。
def __iter__(self):
for word in self.words:
yield word
设计 Iterator 接口时考虑到了惰性: next(my_iterator) 一次生成一个元素。懒惰的反义词是急迫,其实,惰性求值(lazy evaluation)和及早求值(eager evaluation)是编程语言理论方面的技术术语。
目前实现的几版 Sentence 类都不具有惰性,因为init方法急迫地构建好了文本中的单词列表,然后将其绑定到 self.words 属性上。
==re.finditer 函数是 re.findall 函数的惰性版本,返回的不是列表,而是一个生成器==,按需生成 re.MatchObject 实例。如果有很多匹配, re.finditer 函数能节省大量内存。
RE_WORD = re.compile('\w+')
class Sentence(object):
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()
==简单的生成器函数可以替换成生成器表达式,生成器表达式使用括号生成()==。生成器表达式可以理解为列表推导的惰性版本。
a = (i for i in 'ABC')
print(a) # at 0x7f253342b7d8>
所以上面的iter还可以写成这样:
def __iter__(self):
return (match.group() for match in RE_WORD.finditer(self.text))
大多数函数都接受一个断言参数(predicate)。这个参数是个布尔函数,有一个参数,会应用到输入中的每个元素上,用于判断元素是否包含在输出中。
a = filter(lambda i: i > 3, [1, 2, 3, 4, 5])
for i in a:
print(i) # 4, 5
在输入的单个可迭代对象中的各个元素上做计算,然后返回结果。
import itertools
sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
a = list(itertools.accumulate(sample))
# [5, 9, 11, 19, 26, 32, 35, 35, 44, 45]
print(a)
# [(0, 2), (1, 4), (2, 8)]
list(map(lambda a, b: (a, b), range(11), [2, 4, 8]))
a = list(itertools.chain('ABC', range(2)))
# ['A', 'B', 'C', 0, 1]
print(a)
b = list(zip('ABC', range(5)))
# [('A', 0), ('B', 1), ('C', 2)]
print(b)
c = list(itertools.zip_longest('ABC', range(5), fillvalue='?'))
# [('A', 0), ('B', 1), ('C', 2), ('?', 3), ('?', 4)]
print(c)
cy = itertools.cycle('ABC')
# A
print(next(cy))
a = list(itertools.combinations('ABCD', 2))
# [('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]
print(a)
d = list(itertools.groupby('LLLLAAGGG'))
# [('L', object at 0x7fc084b957b8>), ('A', ) , ('G', ) ]
print(d)
函数接受一个可迭代的对象,然后返回单个结果。这些函数叫“归约”函数、 “合拢”函数或“累加”函数。
all([1, 2, 3]) # True
all([1, 0, 3]) # False
any([1, 0, 3]) # True
iter 函数还有一个鲜为人知的用法:传入两个参数,使用常规的函数或任何可调用的对象创建迭代器。这样使用时,第一个参数必须是可调用的对象,用于不断调用(没有参数),产出各个值;第二个值是哨符,这是个标记值,当可调用的对象返回这个值时,触发迭代器抛出 StopIteration 异常,而不产出哨符。
import random
def d6():
return random.randint(1, 6)
d6_iter = iter(d6, 1)
print(d6_iter) #
# for循环会一直执行,知道i的值为1时,停止。
for i in d6_iter:
print(i)