流畅的python——14 可迭代的对象、迭代器和生成器
十四、可迭代的对象、迭代器和生成器
In [28]: import re
在 Iterable 类中定义。
In [29]: import reprlib
In [30]: RE_WORD = re.compile('\w+')
In [31]: 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)
...:
In [32]: s = Sentence('"The time has come," the Walrus said,')
In [33]: s
Out[33]: Sentence('"The time ha... Walrus said,')
In [34]: for w in s:
...: print(w)
...:
The
time
has
come
the
Walrus
said
In [35]: s.words
Out[35]: ['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']
可迭代: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__
方法,因此你也应该这么做。之所以对 __getitem__
方法做特殊处理,是为了向后兼容,而未来可能不会再这么做(不过,写作本书时还未弃用)。
11.2 节提到过,这是鸭子类型(duck typing)的极端形式:不仅要实现特殊的 __iter__
方法,还要实现 __getitem__
方法,而且 __getitem__
方法的参数是从 0 开始的整数(int),这样才认为对象是可迭代的。
在白鹅类型(goose-typing)理论中,可迭代对象的定义简单一些,不过没那么灵活:如果实现了 __iter__
方法,那么就认为对象是可迭代的。此时,不需要创建子类,也不用注册,因为 abc.Iterable 类实现了 __subclasshook__
方法,
>>> class Foo:
... def __iter__(self):
... pass
...
>>> from collections import abc
>>> issubclass(Foo, abc.Iterable)
True
>>> f = Foo()
>>> isinstance(f, abc.Iterable)
True
不过要注意,虽然前面定义的 Sentence 类是可以迭代的,但却无法通过 issubclass (Sentence, abc.Iterable) 测试。
从 Python 3.4 开始,检查对象 x 能否迭代,最准确的方法是:调用 iter(x) 函数,如果不可迭代,再处理 TypeError 异常。这比使用 isinstance(x,abc.Iterable) 更准确,因为 iter(x) 函数会考虑到遗留的 __getitem__
方法,而 abc.Iterable 类则不考虑。
可迭代对象与迭代器的对比
可迭代的对象
使用 iter 内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的 __iter__
方法,那么对象就是可迭代的。序列都可以迭代;实现了 __getitem__
方法,而且其参数是从零开始的索引,这种对象也可以迭代。
可迭代对象和迭代器的关系:Python 从可迭代对象获取迭代器
for 循环内部是有迭代器的,只不过看不到
while 循环实现:
>>> s = 'ABC'
>>> it = iter(s) # 获取迭代器
>>> while True:
... try:
... print(next(it)) # next 获取迭代器下一个元素
... except StopIteration: # 如果没有字符了,迭代器抛出 StopIteration 异常
... del it # 释放对 it 的引用,即废弃迭代器对象
... break # 退出循环
标准的迭代器接口有两个方法:
__next__
返回下一个可用的元素,如果没有元素了,抛出 StopIteration 异常
__iter__
返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如 for 循环中
这个接口在 collections.abc.Iterator 抽象基类中制定。这个类定义了 __next__
抽象方法,而且继承自 Iterable 类;__iter__
抽象方法则在 Iterable 类中定义。
abc.Iterator 类的源码
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__
和 __iter__
两个方法,所以除了调用 next() 方法,以及捕获 StopIteration 异常之外,没有办法检查是否还有遗留的元素。此外,也没有办法“还原”迭代器。如果想再次迭代,那就要调用 iter(...),传入之前构建迭代器的可迭代对象。传入迭代器本身没用,因为前面说过 Iterator.__iter__
方法的实现方式是返回实例本身,所以传入迭代器无法还原已经耗尽的迭代器。
迭代器
实现了无参数的 __next__
方法,返回序列中下一个元素;没有元素抛出 StopIteration 异常
实现了 __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) # 可迭代协议:__iter__ 方法实例化并返回一个迭代器
class SentenceIterator: # 迭代器
def __init__(self, words):
self.words = words
self.index = 0 # 索引从 0 开始
def __next__(self):
try:
word = self.words[self.index]
except IndexError:
raise StopIteration() # 索引越界,抛出 StopIteration 异常
self.index += 1
return word
def __iter__(self): # 返回迭代器本身
return self
注意,对这个示例来说,其实没必要在 SentenceIterator 类中实现 __iter__
方法,不过这么做是对的,因为迭代器应该实现 __next__
和 __iter__
两个方法,而且这么做能让迭代器通过 issubclass(SentenceInterator, abc.Iterator) 测试。
把Sentence变成迭代器:坏主意
构建可迭代的对象和迭代器时经常会出现错误,原因是混淆了二者。要知道,可迭代的对象有个 __iter__
方法,每次都实例化一个新的迭代器;而迭代器要实现 __next__
方法,返回单个元素,此外还要实现 __iter__
方法,返回迭代器本身。
因此,迭代器可以迭代,但是可迭代的对象不是迭代器。
除了 __iter__
方法之外,你可能还想在 Sentence 类中实现 __next__
方法,让Sentence 实例既是可迭代的对象,也是自身的迭代器。可是,这种想法非常糟糕。根据有大量 Python 代码审查经验的 Alex Martelli 所说,这也是常见的反模式。
《设计模式:可复用面向对象软件的基础》一书讲解迭代器设计模式时,在“适用性”一节中说:
迭代器模式可用来:访问一个聚合对象的内容而无需暴露它的内部表示,支持对聚合对象的多种遍历,为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)为了“支持多种遍历”,必须能从同一个可迭代的实例中获取多个独立的迭代器,而且各个迭代器要能维护自身的内部状态,因此这一模式正确的实现方式是,每次调用iter(my_iterable) 都新建一个独立的迭代器。这就是为什么这个示例需要定义SentenceIterator 类。
可迭代的对象一定不能是自身的迭代器。也就是说,可迭代的对象必须实现 __iter__
方法,但不能实现 __next__
方法。
另一方面,迭代器应该一直可以迭代。迭代器的 __iter__
方法应该返回自身。
Sentence类第3版:生成器函数
实现相同功能,但却符合 Python 习惯的方式是,用生成器函数代替 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):
# return iter(self.words)
for word in self.words: # 迭代 self.words
yield word # yield 返回一个元素
return
"""
这个 return 语句不是必要的;这个函数可以直接“落空”,自动返回。不管有没有
return 语句,生成器函数都不会抛出 StopIteration 异常,而是在生成完全部值之后
会直接退出。
"""
# 完成!
生成器函数的工作原理
只要函数的定义体中有 yield 关键字,就是生成器函数。
调用生成器函数,返回一个生成器对象。也就是说,生成器函数是生成器工厂。
生成器函数的定义体执行完毕后,生成器对象会抛出 StopIteration 异常。
生成器函数定义体中的 return 语句会触发生成器对象抛出 StopIteration 异常。
Sentence类第4版:惰性实现
re.finditer 函数是 re.findall 函数的惰性版本,返回的不是列表,而是一个生成器,按需生成 re.MatchObject 实例。
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()
Sentence类第5版:生成器表达式
>>> def gen_AB(): # 生成器函数
... print('start')
... yield 'A'
... print('continue')
... yield 'B'
... print('end.')
...
>>> 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 i in res2: # 惰性迭代,只有真正迭代的时候,从会执行生成器
... print('-->', i)
...
start
--> AAA
continue
--> BBB
end.
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): # 不是生成器函数,而是返回生成器,最终结果都是调用 iter 方法得到一个生成器对象
# 生成器表达式,语法糖,可以替换为生成器函数
return (match.group() for match in RE_WORD.finditer(self.text))
何时使用生成器表达式
句法提示
生成器作为圆括号的参数,写一个圆括号即可。Vector(n * scalar for n in self)
等差数列生成器
典型的迭代器模式作用很简单——遍历数据结构。
即便不是从集合中获取元素,而是获取序列中即使产生的下一个值时,也用得到这种基于方法的标准接口。
例如,内置的range 函数用于生成有穷整数等差数列(Arithmetic Progression,AP),itertools.count 函数用于生成无穷等差数列。
In [15]: class A:
...: def __init__(self,begin,step,end):
...: self._list = []
...: self.begin = begin
...: self.step = step
...: self.end = end
...: self.init_num_list()
...: def init_num_list(self):
...: self._list = (i for i in range(self.begin,self.end,self.step))
...: def __getitem__(self,i):
...: return self._list[i]
...:
In [16]: a = A(1,2,3)
In [17]: a
Out[17]: <__main__.A at 0x17ef1c21eb8>
In [18]: list(a)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-18-e8d606e78fd4> in <module>
----> 1 list(a)
<ipython-input-15-c397eaa5223f> in __getitem__(self, i)
9 self._list = (i for i in range(self.begin,self.end,self.step))
10 def __getitem__(self,i):
---> 11 return self._list[i]
12
TypeError: 'generator' object is not subscriptable
In [19]: class A:
...: def __init__(self,begin,step,end):
...: self._list = []
...: self.begin = begin
...: self.step = step
...: self.end = end
...: self.init_num_list()
...: def init_num_list(self):
...: self._list = (i for i in range(self.begin,self.end,self.step))
...: def __iter__(self):
...: return self._list
In [10]: class A:
...: def __init__(self,begin,step,end=None):
...: self._list = []
...: self.begin = begin
...: self.step = step
...: self.end = end
...: self.init_num_list()
...: def init_num_list(self):
...: i = self.begin
...: while True:
...: if self.end and i > self.end:return
...: yield i
...: i += self.step
...: def __iter__(self):
...: return self.init_num_list()
In [11]: a = A(1,1/3,10)
In [12]: list(a)
Out[12]:
[1,
1.3333333333333333,
1.6666666666666665,
1.9999999999999998,
2.333333333333333,
2.6666666666666665,
3.0,
3.3333333333333335,
3.666666666666667,
4.0,
4.333333333333333,
4.666666666666666,
4.999999999999999,
5.333333333333332,
5.666666666666665,
5.999999999999998,
6.333333333333331,
6.666666666666664,
6.999999999999997,
7.33333333333333,
7.666666666666663,
7.9999999999999964,
8.33333333333333,
8.666666666666664,
8.999999999999998,
9.333333333333332,
9.666666666666666,
10.0]
In [16]: class B:
...: def __init__(self,begin,step,end=None):
...: self.begin=begin
...: self.step = step
...: self.end = end
...: def __iter__(self):
...: result = type(self.begin + self.step)(self.begin) # 强制转为前面的类型
...: forever = self.end is None
...: index = 0
...: while forever or result < self.end: # 如果 end 是 None 则无限
...: yield result
...: index += 1
...: result = self.begin + index * self.step
...:# 最后一行不是累加 result,而是使用 index 变量,以此降低处理浮点数时累积效应导致错误的风险。
# 1/3 + 1/3 + 1/3 与 3 * 1/3
# 所以,为了让数列的首项与其他项的类型一样,我能想到最好的方式是,先做加法运算,然后使用计算结果的类型强制转换生成的结果。
In [17]:
In [17]: b = B(1,1/3,10)
In [18]: list(b)
Out[18]:
[1.0,
1.3333333333333333,
1.6666666666666665,
2.0,
2.333333333333333,
2.6666666666666665,
3.0,
3.333333333333333,
3.6666666666666665,
4.0,
4.333333333333333,
4.666666666666666,
5.0,
5.333333333333333,
5.666666666666666,
6.0,
6.333333333333333,
6.666666666666666,
7.0,
7.333333333333333,
7.666666666666666,
8.0,
8.333333333333332,
8.666666666666666,
9.0,
9.333333333333332,
9.666666666666666]
生成器函数
In [17]: def create_num_list(begin,step,end=None):
...: index = 0
...: res = begin
...: while end is None or res < end:
...: yield res
...: index += 1
...: res = begin + index*step
In [35]: next(c)
Out[35]: 14466
使用 itertools 模块生成等差数列
itertools.count
In [17]: import itertools
In [18]: gen = itertools.count(1,3) # start 和 step 值
In [19]: next(gen)
Out[19]: 1
In [20]: next(gen)
Out[20]: 4
In [21]: next(gen)
Out[21]: 7
然而,itertools.count 函数从不停止,因此,如果调用 list(count()),Python 会创建一个特别大的列表,超出可用内存,在调用失败之前,电脑会疯狂地运转。
itertools.takewhile
它会生成一个使用另一个生成器的生成器,在指定的条件计算结果为 False 时停止。
In [39]: gen = itertools.takewhile(lambda n:n<3,itertools.count(1,.5))
In [40]: list(gen)
Out[40]: [1, 1.5, 2.0, 2.5]
使用这两个生成器重写等差数列生成器函数
In [41]: def create_num_list(begin,step,end=None):
...: first = type(begin+step)(begin)
...: gen = itertools.count(begin,step)
...: if end is not None:
...: gen = itertools.takewhile(lambda n : n < end,gen)
...: return gen
...:
In [42]: c = create_num_list(1,2)
In [43]: next(c)
Out[43]: 1
In [44]: next(c)
Out[44]: 3
In [45]: next(c)
Out[45]: 5
In [46]: c = create_num_list(1,3,10)
In [47]: list(c)
Out[47]: [1, 4, 7]
此时,create_num_list 不是生成器函数,因为定义体中没有 yield
关键字。但是,它会返回一个生成器,因此它与其他生成器函数一样,也是生成器工厂函数。
生成器函数:yield
关键字。生成器,支持 next。
标准库中的生成器函数
os.walk
函数遍历目录树,产出文件名,像for循环一样简单。
过滤功能的生成器函数:从输入的可迭代对象中产出元素的子集,而且不修改元素本身。
与 takewhile 函数一样,表 14-1 中的大多数函数都接受一个断言参数(predicate)。这个参数是个布尔函数,有一个参数,会应用到输入中的每个元素上,用于判断元素是否包含在输出中。
In [48]: def v(c):
...: return c.lower() in 'aeiou'
...:
In [49]: a = filter(v,'abcde')
In [50]: a
Out[50]: <filter at 0x2471e613588>
In [51]: list(a)
Out[51]: ['a', 'e']
In [52]: list(itertools.filterfalse(v,'abcde'))
Out[52]: ['b', 'c', 'd']
In [53]: list(itertools.dropwhile(v,'abcde'))
Out[53]: ['b', 'c', 'd', 'e']
In [54]: list(itertools.takewhile(v,'abcde'))
Out[54]: ['a']
In [55]: list(itertools.compress('abcde',(1,0,0,1,0,1)))
Out[55]: ['a', 'd']
In [57]: b = itertools.islice('abcde',3)
In [58]: b
Out[58]: <itertools.islice at 0x2471e4988b8>
In [59]: list(b)
Out[59]: ['a', 'b', 'c']
In [60]: list(itertools.islice('abcde',2,5))
Out[60]: ['c', 'd', 'e']
In [61]: list(itertools.islice('abcde',1,5,2))
Out[61]: ['b', 'd']
映射的生成器函数:在输入的可迭代对象的各个元素上做计算,返回结果。多个可迭代对象,第一个可迭代对象到头后,就停止输出。
In [62]: s = [1,3,2,4,5,7,6,8,9,0]
In [63]: list(itertools.accumulate(s,min))
Out[63]: [1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
In [64]: list(itertools.accumulate(s,max))
Out[64]: [1, 3, 3, 4, 5, 7, 7, 8, 9, 9]
In [65]: import operator
In [66]: list(itertools.accumulate(s,operator.mul))
Out[66]: [1, 3, 6, 24, 120, 840, 5040, 40320, 362880, 0]
In [67]: list(itertools.accumulate(range(0,11),operator.mul))
Out[67]: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
In [68]: list(itertools.accumulate(range(1,11),operator.mul)) # 计算 1! 到 10!
Out[68]: [1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
In [69]: list(enumerate('abcde',1)) # 从 1 开始,枚举编号
Out[69]: [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd'), (5, 'e')]
In [70]: list(map(operator.mul , range(11),range(11))) # 平方
Out[70]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
In [71]: list(map(operator.mul , range(11), [2,4,8])) # 计算对应位置的积,以元素少的长度为准
Out[71]: [0, 4, 16]
In [72]: list(map(lambda a,b : (a,b),range(11),[1,2,3])) # 作用等同于内置的 zip
Out[72]: [(0, 1), (1, 2), (2, 3)]
In [73]: list(itertools.starmap(operator.mul, enumerate('abcde',1)))
Out[73]: ['a', 'bb', 'ccc', 'dddd', 'eeeee']
# 计算平均值
In [74]: list(itertools.starmap(lambda a,b:b/a, enumerate(itertools.accumulate(s),1)))
Out[74]: [1.0, 2.0, 2.0, 2.5, 3.0, 3.6666666666666665, 4.0, 4.5, 5.0, 4.5]
合并的生成器函数:从输入的多个可迭代对象中产出元素。
chain
和 chain.from_iterable
按顺序(一个接一个)处理输入的可迭代对象,而 product
、zip
和 zip_longest
并行处理输入的各个可迭代对象。
In [75]: list(itertools.chain('abcde',range(5)))
Out[75]: ['a', 'b', 'c', 'd', 'e', 0, 1, 2, 3, 4]
In [76]: list(itertools.chain(enumerate('abcde')))
Out[76]: [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e')]
In [77]: list(enumerate('abcde'))
Out[77]: [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e')]
In [78]: list(itertools.chain.from_iterable(enumerate('abcde'))) # 数据来源:[[],[]]
Out[78]: [0, 'a', 1, 'b', 2, 'c', 3, 'd', 4, 'e']
In [79]: list(zip('abcde',range(10))) # 以长度短的为主
Out[79]: [('a', 0), ('b', 1), ('c', 2), ('d', 3), ('e', 4)]
In [81]: list(zip('abcde',range(10),range(10,100)))
Out[81]: [('a', 0, 10), ('b', 1, 11), ('c', 2, 12), ('d', 3, 13), ('e', 4, 14)]
In [82]: list(itertools.zip_longest('abcde',range(10))) # 长度不够,填充 None
Out[82]:
[('a', 0),
('b', 1),
('c', 2),
('d', 3),
('e', 4),
(None, 5),
(None, 6),
(None, 7),
(None, 8),
(None, 9)]
In [83]: list(itertools.zip_longest('abcde',range(10),fillvalue='*')) # 指定填充 *
Out[83]:
[('a', 0),
('b', 1),
('c', 2),
('d', 3),
('e', 4),
('*', 5),
('*', 6),
('*', 7),
('*', 8),
('*', 9)]
In [84]: list(itertools.zip_longest('abcde',range(10),range(10,15),fillvalue='*'))
Out[84]:
[('a', 0, 10),
('b', 1, 11),
('c', 2, 12),
('d', 3, 13),
('e', 4, 14),
('*', 5, '*'),
('*', 6, '*'),
('*', 7, '*'),
('*', 8, '*'),
('*', 9, '*')]
itertools.product
生成器是计算笛卡儿积的惰性方式;
多个 for 子句中使用列表推导计算过笛卡儿积。
也可以使用包含多个 for 子句的生成器表达式,以惰性方式计算笛卡儿积。
In [85]: list(itertools.product('abcde',range(2)))
Out[85]:
[('a', 0),
('a', 1),
('b', 0),
('b', 1),
('c', 0),
('c', 1),
('d', 0),
('d', 1),
('e', 0),
('e', 1)]
In [86]: list(itertools.product('abcde'))
Out[86]: [('a',), ('b',), ('c',), ('d',), ('e',)]
In [87]: list(itertools.product('abcde',repeat=2)) # 重复两个输入的可迭代对象
Out[87]:
[('a', 'a'),
('a', 'b'),
('a', 'c'),
('a', 'd'),
('a', 'e'),
('b', 'a'),
('b', 'b'),
('b', 'c'),
('b', 'd'),
('b', 'e'),
('c', 'a'),
('c', 'b'),
('c', 'c'),
('c', 'd'),
('c', 'e'),
('d', 'a'),
('d', 'b'),
('d', 'c'),
('d', 'd'),
('d', 'e'),
('e', 'a'),
('e', 'b'),
('e', 'c'),
('e', 'd'),
('e', 'e')]
In [88]: list(itertools.product('ab',repeat=3))
Out[88]:
[('a', 'a', 'a'),
('a', 'a', 'b'),
('a', 'b', 'a'),
('a', 'b', 'b'),
('b', 'a', 'a'),
('b', 'a', 'b'),
('b', 'b', 'a'),
('b', 'b', 'b')]
In [89]: list(itertools.product('ab','ab','ab'))
Out[89]:
[('a', 'a', 'a'),
('a', 'a', 'b'),
('a', 'b', 'a'),
('a', 'b', 'b'),
('b', 'a', 'a'),
('b', 'a', 'b'),
('b', 'b', 'a'),
('b', 'b', 'b')]
In [90]: list(itertools.product('ab','c',repeat=2))
Out[90]:
[('a', 'c', 'a', 'c'),
('a', 'c', 'b', 'c'),
('b', 'c', 'a', 'c'),
('b', 'c', 'b', 'c')]
从一个元素产出多个值的生成器对象,扩展输入的可迭代对象。
In [91]: ct = itertools.count()
In [92]: next(ct)
Out[92]: 0
In [93]: next(ct)
Out[93]: 1
In [94]: next(ct)
Out[94]: 2
In [95]: list(itertools.islice(itertools.count(1,.3),3))
Out[95]: [1, 1.3, 1.6]
In [96]: cy = itertools.cycle('abcde')
In [97]: next(cy)
Out[97]: 'a'
In [98]: next(cy)
Out[98]: 'b'
In [99]: list(itertools.islice(cy,5))
Out[99]: ['c', 'd', 'e', 'a', 'b']
In [100]: list(itertools.islice(cy,10))
Out[100]: ['c', 'd', 'e', 'a', 'b', 'c', 'd', 'e', 'a', 'b']
In [101]: rp = itertools.repeat(7)
In [102]: next(rp)
Out[102]: 7
In [103]: next(rp)
Out[103]: 7
In [104]: next(rp)
Out[104]: 7
In [105]: list(itertools.repeat(8,4))
Out[105]: [8, 8, 8, 8]
In [106]: list(map(operator.mul,range(11),itertools.repeat(5)))
Out[106]: [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]
combinations, combinations_with_replacement, permutations, product 称为组合学生成器。
In [1]: import itertools
# 'ABC' 中每两个元素(len()==2)的各种组合;在生成的元组中,元素的顺序无关紧要(可以视作集合)。
In [2]: list(itertools.combinations('ABC',2))
Out[2]: [('A', 'B'), ('A', 'C'), ('B', 'C')]
# 'ABC' 中每两个元素(len()==2)的各种组合,包括相同元素的组合。
In [3]: list(itertools.combinations_with_replacement('ABC',2))
Out[3]: [('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')]
# 'ABC' 中每两个元素(len()==2)的各种排列;在生成的元组中,元素的顺序有重要意义。
In [4]: list(itertools.permutations('ABC',2))
Out[4]: [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
# 'ABC' 和 'ABC'(repeat=2 的效果)的笛卡儿积。
In [5]: list(itertools.product('ABC',repeat=2))
Out[5]:
[('A', 'A'),
('A', 'B'),
('A', 'C'),
('B', 'A'),
('B', 'B'),
('B', 'C'),
('C', 'A'),
('C', 'B'),
('C', 'C')]
用于产出输入的可迭代对象中的全部元素,会以某种方式排列。
In [6]: list(itertools.groupby('LLLLLAAGGG'))
Out[6]:
[('L', <itertools._grouper at 0x1911bc04128>),
('A', <itertools._grouper at 0x1911bd38be0>),
('G', <itertools._grouper at 0x1911bd38748>)]
In [7]: for char , g in itertools.groupby('LLLLAAGG'):
...: print(char, '->' ,list(g))
...:
L -> ['L', 'L', 'L', 'L']
A -> ['A', 'A']
G -> ['G', 'G']
In [11]: a = ['a','bc','fdfc','afafd','fadfasfdae']
In [12]: a.sort(key=len)
In [13]: a
Out[13]: ['a', 'bc', 'fdfc', 'afafd', 'fadfasfdae']
In [14]: itertools.groupby(a,len)
Out[14]: <itertools.groupby at 0x1911ba3eae8>
In [15]: for l,g in itertools.groupby(a,len):
...: print(l,'->',list(g))
...:
1 -> ['a']
2 -> ['bc']
4 -> ['fdfc']
5 -> ['afafd']
10 -> ['fadfasfdae']
In [17]: for lr , g in itertools.groupby(reversed(a), len):
...: print(lr,'->',list(g))
...:
...:
10 -> ['fadfasfdae']
5 -> ['afafd']
4 -> ['fdfc']
2 -> ['bc']
1 -> ['a']
iterator.tee : 产出多个相同的生成器,每个生成器可以单独使用。
In [18]: list(itertools.tee('ABC'))
Out[18]: [<itertools._tee at 0x1911bcec8c8>, <itertools._tee at 0x1911bcecf48>]
In [19]: t1 , t2 = list(itertools.tee('ABC'))
In [20]: next(t1)
Out[20]: 'A'
In [21]: next(t1)
Out[21]: 'B'
In [22]: next(t2)
Out[22]: 'A'
Python3.3 新句法:yield from
如果生成器函数需要产出另一个生成器生成的值,传统的方法是 :嵌套的 for 循环。
>>> def chain(*iterables):
... for it in iterables:
... for i in it:
... yield i
...
>>> s = 'ABC'
>>> t = tuple(range(3))
>>> list(chain(s, t))
['A', 'B', 'C', 0, 1, 2]
chain 生成器函数把操作依次交给接收到的各个可迭代对象处理。
为此,“PEP 380 —
Syntax for Delegating to a Subgenerator”(https://www.python.org/dev/peps/pep-0380/)引入了一个新句法:
>>> def chain(*iterables):
... for i in iterables:
... yield from i
...
>>> list(chain(s, t))
['A', 'B', 'C', 0, 1, 2]
yield from
完全替代了内层的 for 循环。代码更流畅。更像语法糖。
除了替代循环之外, yield from
还会创建通道,把内层生成器直接与外层生成器的客户端联系起来。
把生成器当成协程使用,这个通道特别重要,不仅能为客户端代码生成值,还能为客户端代码生成值。
可迭代的归约函数
接受一个可迭代的对象,然后返回单个结果。这些函数叫“归约”函数、“合拢”函数或“累加”函数。其实,这里列出的每个内置函数都可以使用 functools.reduce 函数实现,内置是因为使用它们便于解决常见的问题。此外,对 all 和 any 函数来说,有一项重要的优化措施是 reduce 函数做不到的:这两个函数会短路(即一旦确定了结果就立即停止使用迭代器)。
* 也可以像这样调用:max(arg1, arg2, ..., [key=?]),此时返回参数中的最大值。
# 也可以像这样调用:min(arg1, arg2, ..., [key=?]),此时返回参数中的最小值。
In [23]: all([])
Out[23]: True
In [24]: all([1,2,3])
Out[24]: True
In [25]: all([1,0])
Out[25]: False
In [26]: any([])
Out[26]: False
In [27]: any([1,2,3])
Out[27]: True
In [28]: any([1,0])
Out[28]: True
In [29]: any([0,0])
Out[29]: False
In [30]: g = (i for i in [0,1,2])
In [31]: any(g)
Out[31]: True
In [32]: list(g) # 短路运算
Out[32]: [2]
还有一个内置的函数接受一个可迭代的对象,返回不同的值——sorted。reversed 是生成器函数,与此不同,sorted 会构建并返回真正的列表。
毕竟,要读取输入的可迭代对象中的每一个元素才能排序,而且排序的对象是列表,因此 sorted 操作完成后返回排序后的列表。我在这里提到 sorted,是因为它可以处理任意的可迭代对象。
In [33]: a = [0,3,1]
In [34]: sorted(a)
Out[34]: [0, 1, 3]
In [35]: reversed(a)
Out[35]: <list_reverseiterator at 0x1911bdd0cc0>
当然,sorted 和这些归约函数只能处理最终会停止的可迭代对象。否则,这些函数会一直收集元素,永远无法返回结果。
深入分析 iter 函数
iter 函数的另外一种用法:传入两个参数:
可调用对象,不断调用,产生值,没有参数;
哨符:标记值,当可调用对象返回这个值,触发 StopIteration 异常,不产出哨符。
In [36]: from random import randint
In [37]: def d6():
...: return randint(1,6)
...:
In [38]: d6_iter = iter(d6,1)
In [39]: for r in d6_iter:
...: print(r)
...:
3
3
5
6
5
5
5
In [40]: d6_iter
Out[40]: <callable_iterator at 0x1911bcb43c8>
内置函数 iter 的文档(https://docs.python.org/3/library/functions.html#iter)中有个实用的例子。这段代码逐行读取文件,直到遇到空行或者到达文件末尾为止:
with open('mydata.txt') as fp:
for line in iter(fp.readline, '\n'):
process_line(line)
案例分析:在数据库转换工具中使用生成器
读写分离解耦
读 iso文件(ISO-2709格式),mst文件,xml文件,每次读取一个记录,生成一个字典,这是一些读生成器。
统一写入 json 数据,引用一个生成器,每次输出一个记录,存入文件。
使用生成器的话,可以交叉读写,因此这个脚本可以处理任意大小的文件。
这不是什么尖端科技,可是通过这个实例我们看到了生成器的灵活性。使用生成器处理数据库时,我们把记录看成数据流,这样消耗的内存量最低,而且不管数据有多大都能处理。只要管理着大型数据集,都有可能在实践中找到机会使用生成器。
迭代器与生成器
In [1]: a = 'ab'
In [2]: iter(a)
Out[2]: <str_iterator at 0x1f0fff15860>
In [3]: next(a)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-15841f3f11d4> in <module>
----> 1 next(a)
TypeError: 'str' object is not an iterator
In [4]: len(a)
Out[4]: 2
In [5]: b = (i for i in a)
In [6]: len(b)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-97d8916a185b> in <module>
----> 1 len(b)
TypeError: object of type 'generator' has no len()
In [7]: len(iter(b))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-7-9b431dc76321> in <module>
----> 1 len(iter(b))
TypeError: object of type 'generator' has no len()
In [8]: len(list(b))
Out[8]: 2
In [13]: from collections import abc
In [14]: isinstance(enumerate(a),abc.Iterator)
Out[14]: True
In [16]: import types
In [18]: isinstance(enumerate(a),types.GeneratorType)
Out[18]: False
In [19]: b
Out[19]: <generator object <genexpr> at 0x000001F0FFF037D8>
In [20]: isinstance(b,types.GeneratorType)
Out[20]: True
1 接口定义方面
迭代器协议:实现了两个方法:__next__
和 __iter__
生成器实现了这两个方法,所以,所有生成器都是迭代器。 abc.Iterator
类型
2 实现方式方面
生成器:含有 yield
参数 或 生成器表达式。types.GeneratorType
类型
调用生成器函数或者执行生成器表达式得到的生成器对象属于语言内部的 GeneratorType 类型
该类型实现了 迭代器协议。
定义:生成器——迭代器对象的类型,调用生成器函数时生成。
enumerate
是迭代器,但不是生成器。
3 概念方面
迭代器模式:迭代器用于遍历集合,从中产出元素。数据源是现有的,迭代器不能修改从数据源中读取的值,只能原封不动地产出值。
生成器 可能无需遍历集合就能生成值,例如 range
函数。即便依附了集合,生成器不仅能产出集合中的元素,还可以派生自元素的其他值。例如 enumerate
函数。
根据迭代器模式的原始定义,enumerate
函数返回的生成器不是迭代器,因为产出了元组。但是,不是 GeneratorType
类型
从概念方面来看,实现方式无关紧要。不使用 python生成器对象也能编写生成器。
不使用 GeneratorType
实例实现斐波那契数列生成器
In [25]: class F:
...: def __init__(self):
...: self.a = 0
...: self.b = 1
...: def __next__(self):
...: res = self.a
...: self.a , self.b = self.b, self.a + self.b
...: return res
...:
In [26]: class Fc:
...: def __iter__(self):
...: return F()
...:
In [27]: for i in Fc():
...: print(i)
...: import time
...: time.sleep(1)
...:
0
1
1
2
3
5
8
13
可行,但是,不符合 Python 风格。符合 Python 风格的方式如下:
In [28]: def f():
...: a,b = 0,1
...: while True:
...: yield a
...: a,b = b,a+b
...:
In [29]: for i in f():
...: print(i)
...: import time
...: time.sleep(1)
...:
0
1
1
2
3
5
8
13
当然,始终可以使用生成器这个语言结构履行迭代器的基本职责:遍历集合,并从中产出元素。
事实上,Python 程序员不会严格区分二者,即便在官方文档中也把生成器称作迭代器。 Python 词汇表(https://docs.python.org/dev/glossary.html#term-iterator)对迭代器下的权威定义比较笼统,涵盖了迭代器和生成器。
迭代器:表示数据流的对象……
建议你读一下 Python 词汇表中对迭代器的完整定义(https://docs.python.org/3/glossary.html#term-iterator)。而在生成器的定义中(https://docs.python.org/3/glossary.html#term-generator),迭代器和生成器是同义词,“生成器”指代生成器函数,以及生成器函数构建的生成器对象。因此,在 Python社区的行话中,迭代器和生成器在一定程度上是同义词。
Python 中最简的迭代器接口
《设计模式:可复用面向对象软件的基础》一书讲解迭代器模式时,在“实现”一节中说道:
迭代器的最小接口由 First、Next、IsDone 和 CurrentItem 操作组成。不过,这句话有个脚注:
甚至可以将 Next、IsDone 和 CurrentItem 并入到一个操作中,该操作前进到下一个对象并返回这个对象,如果遍历结束,那么这个操作返回一个特定的值(例如,0)标志该迭代结束。这样我们就使这个接口变得更小了。
这与 Python 的做法接近:只用一个 __next__
方法完成这项工作。不过,为了表明迭代结束,这个方法没有使用哨符,因为哨符可能不小心被忽略,而是使用 StopIteration 异常。简单且正确,这正是 Python 之道。