可迭代的对象、迭代器和生成器

迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据项的方式,即按需一次获取一个数据项。这就是迭代器模式(Iterator pattern)。

迭代器用于从集合中取出元素;而生成器用于“凭空”生成元素。通过斐波纳契数列能很好地说明二者之间的区别:斐波纳契数
列中的数有无穷个,在一个集合里放不下。不过要知道,在 Python社区中,大多数时候都把迭代器和生成器视作同一概念。

Sentence类

单词序列

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) ➍

❶re.findall 函数返回一个字符串列表,里面的元素是正则表达式的
全部非重叠匹配。
❷ self.words 中保存的是 .findall 函数返回的结果,因此直接返回
指定索引位上的单词。
❸ 为了完善序列协议,我们实现了 __len__ 方法;不过,为了让对象
可以迭代,没必要实现这个方法。
❹ reprlib.repr 这个实用函数用于生成大型数据结构的简略字符串表
示形式。

示例 14-2 测试 Sentence 实例能否迭代

>>> s = Sentence('"The time has come," the Walrus said,') # ➊
>>> s
Sentence('"The time ha... Walrus said,') # ➋
>>> for word in s: # ➌
... print(word)
The
time
has
come
the
Walrus
said
>>> list(s) # ➍
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']

❶ 传入一个字符串,创建一个 Sentence 实例。
❷ 注意,__repr__ 方法的输出中包含 reprlib.repr 方法生成的
...。
❸ Sentence 实例可以迭代,稍后说明原因。
❹ 因为可以迭代,所以 Sentence 对象可以用于构建列表和其他可迭代
的类型。

序列可以迭代的原因:iter函数

解释器需要迭代对象 x 时,会自动调用 iter(x)。内置的 iter 函数有以下作用。

  • (1) 检查对象是否实现了 __iter__ 方法,如果实现了就调用它,获取一个迭代器。
  • (2) 如果没有实现 __iter__ 方法,但是实现了 __getitem__ 方法,Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素。
  • (3) 如果尝试失败,Python 抛出 TypeError 异常,通常会提示“C objectis not iterable”(C 对象不可迭代),其中 C 是目标对象所属的类。任何 Python 序列都可迭代的原因是,它们都实现了 __getitem__ 方法。其实,标准的序列也都实现了 __iter__ 方法,因此你也应该这么做。之所以对 __getitem__ 方法做特殊处理,是为了向后兼容,而未来可能不会再这么做

可迭代的对象与迭代器的对比

可迭代的对象
  使用 iter 内置函数可以获取迭代器的对象。如果对象实现了能返回迭代器的 __iter__ 方法,那么对象就是可迭代的。序列都可以迭代;实现了 __getitem__ 方法,而且其参数是从零开始的索引,这种
对象也可以迭代。

我们要明确可迭代的对象和迭代器之间的关系:Python 从可迭代的对象中获取迭代器。

下面是一个简单的 for 循环,迭代一个字符串。这里,字符串 'ABC'是可迭代的对象。背后是有迭代器的,只不过我们看不到:

>>> s = 'ABC'
>>> for char in s:
... print(char)
...
A
B
C
###如果没有 for 语句,不得不使用 while 循环模拟,要像下面这样写:
>>> s = 'ABC'
>>> it = iter(s) # ➊
>>> while True:
... try:
... print(next(it)) # ➋
... except StopIteration: # ➌
... del it # ➍
... break # ➎
...
A
B
C

❶ 使用可迭代的对象构建迭代器 it。
❷ 不断在迭代器上调用 next 函数,获取下一个字符。
❸ 如果没有字符了,迭代器会抛出 StopIteration 异常。
❹ 释放对 it 的引用,即废弃迭代器对象。
❺ 退出循环。

StopIteration 异常表明迭代器到头了。Python 语言内部会处理 for循环和其他迭代上下文(如列表推导、元组拆包,等等)中的StopIteration 异常。

标准的迭代器接口有两个方法。
__next__
  返回下一个可用的元素,如果没有元素了,抛出 StopIteration异常。
__iter__
  返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。

迭代器

迭代器是这样的对象:实现了无参数的 __next__ 方法,返回序列中的下一个元素;如果没有元素了,那么抛出 StopIteration 异常。Python 中的迭代器还实现了 __iter__ 方法,因此迭代器也可以迭代。

典型的迭代器

使用迭代器模式实现 Sentence 类

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): #与前一版相比,这里只多了一个 __iter__ 方法。这一版没有
#__getitem__ 方法,为的是明确表明这个类可以迭代,因为实现了
#__iter__ 方法。
return SentenceIterator(self.words) #根据可迭代协议,__iter__ 方法实例化并返回一个迭代器。
class SentenceIterator:
def __init__(self, words):
self.words = words #SentenceIterator 实例引用单词列表。
self.index = 0#self.index 用于确定下一个要获取的单词。
def __next__(self):
try:
word = self.words[self.index] #获取 self.index 索引位上的单词。
except IndexError:
raise StopIteration() #如果 self.index 索引位上没有单词,那么抛出 StopIteration 异
常。
self.index += 1
return word
def __iter__(self): #实现 self.__iter__ 方法。
return self

生成器函数

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

生成器函数的工作原理
只要 Python 函数的定义体中有 yield 关键字,该函数就是生成器函数。调用生成器函数时,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。

惰性实现

目前实现的几版 Sentence 类都不具有惰性,因为 __init__ 方法急迫地构建好了文本中的单词列表,然后将其绑定到 self.words 属性上。
这样就得处理整个文本,列表使用的内存量可能与文本本身一样多(或许更多,这取决于文本中有多少非单词字符)。如果只需迭代前几个单词,大多数工作都是白费力气。
只要使用的是 Python 3,思索着做某件事有没有懒惰的方式,答案通常都是肯定的。

re.finditer 函数是 re.findall 函数的惰性版本,返回的不是列表,而是一个生成器,按需生成 re.MatchObject 实例。如果有很多匹配,re.finditer 函数能节省大量内存。我们要使用这个函数让第 4版 Sentence 类变得懒惰,即只在需要时才生成下一个单词。代码如示
例 14-7 所示。示例 14-7 sentence_gen2.py: 在生成器函数中调用 re.finditer生成器函数,实现 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):
for match in RE_WORD.finditer(self.text):
yield match.group()

❶ 不再需要 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()] # ➋
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.

❶ gen_AB 函数与示例 14-6 中的一样。
❷ 列表推导迫切地迭代 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 函数的输出夹杂在一起。

使用生成器表达式实现 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))

这里不是生成器函数了(没有 yield),而是使用生成器表达式构建生成器,然后将其返回。不过,最终的效果一样:调用 __iter__ 方法会得到一个生成器对象。
生成器表达式是语法糖:完全可以替换成生成器函数,不过有时使用生成器表达式更便利。

标准库中的生成器函数

用于过滤的生成器函数

模块 函数 说明
itertools

compress(it,
selector_it)

并行处理两个可迭代的对象;如果 selector_it
中的元素是真值,产出 it 中对应的元素

itertools

dropwhile(predicate,
it)

处理 it ,跳过 predicate 的计算结果为真值的元
素,然后产出剩下的各个元素(不再进一步检
查)

内置 filter(predicate, it)

把 it 中的各个元素传给 predicate ,如果
predicate(item) 返回真值,那么产出对应的元
素;如果 predicate 是 None ,那么只产出真值元

itertools

filterfalse(predicate,
it)

与 filter 函数的作用类似,不过 predicate 的
逻辑是相反的: predicate 返回假值时产出对应
的元素

itertools

islice(it, stop) 或
islice(it, start,
stop, step=1)

产出 it 的切片,作用类似于 s[:stop] 或
s[start:stop:step] ,不过 it 可以是任何可迭代
的对象,而且这个函数实现的是惰性操作

itertools

takewhile(predicate,
it)

predicate 返回真值时产出对应的元素,然后立
即停止,不再继续检查

>>> def vowel(c):
... return c.lower() in 'aeiou'
...
>>> list(filter(vowel, 'Aardvark'))
['A', 'a', 'a']
>>> import itertools
>>> list(itertools.filterfalse(vowel, 'Aardvark'))
['r', 'd', 'v', 'r', 'k']
>>> list(itertools.dropwhile(vowel, 'Aardvark'))
['r', 'd', 'v', 'a', 'r', 'k']
>>> list(itertools.takewhile(vowel, 'Aardvark'))
['A', 'a']
>>> list(itertools.compress('Aardvark', (1,0,1,1,0,1)))
['A', 'r', 'd', 'a']
>>> list(itertools.islice('Aardvark', 4))
['A', 'a', 'r', 'd']
>>> list(itertools.islice('Aardvark', 4, 7))
['v', 'a', 'r']
>>> list(itertools.islice('Aardvark', 1, 7, 2))
['a', 'd', 'a']

用于映射的生成器函数

在输入的单个可迭代对象(map 和starmap 函数处理多个可迭代的对象)中的各个元素上做计算,然后返回结果

模块 函数 说明
itertools

accumulate(it,
[func])

产出累积的总和;如果提供了 func ,那么把前两个
元素传给它,然后把计算结果和下一个元素传给
它,以此类推,最后产出结果

内置

enumerate(iterable,
start=0)

产出由两个元素组成的元组,结构是 (index,
item) ,其中 index 从 start 开始计数, item 则从
iterable 中获取

内置

map(func, it1,
[it2, ..., itN])

把 it 中的各个元素传给func,产出结果;如果传入
N 个可迭代的对象,那么 func 必须能接受 N 个参
数,而且要并行处理各个可迭代的对象

itertools starmap(func, it)

把 it 中的各个元素传给 func ,产出结果;输入的
可迭代对象应该产出可迭代的元素 iit ,然后以
func(*iit) 这种形式调用 func

#演示 itertools.accumulate 生成器函数
>>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
>>> import itertools
>>> list(itertools.accumulate(sample)) # ➊
[5, 9, 11, 19, 26, 32, 35, 35, 44, 45]
12
12
>>> list(itertools.accumulate(sample, min)) # ➋
[5, 4, 2, 2, 2, 2, 2, 0, 0, 0]
>>> list(itertools.accumulate(sample, max)) # ➌
[5, 5, 5, 8, 8, 8, 8, 8, 9, 9]
>>> import operator
>>> list(itertools.accumulate(sample, operator.mul)) # ➍
[5, 20, 40, 320, 2240, 13440, 40320, 0, 0, 0]
>>> list(itertools.accumulate(range(1, 11), operator.mul))
[1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800] # ➎

❶ 计算总和。
❷ 计算最小值。
❸ 计算最大值。
❹ 计算乘积。
❺ 从 1! 到 10!,计算各个数的阶乘。

演示用于映射的生成器函数

>>> list(enumerate('albatroz', 1)) # ➊
[(1, 'a'), (2, 'l'), (3, 'b'), (4, 'a'), (5, 't'), (6, 'r'), (7, 'o'), (8, 'z')]
>>> import operator
>>> list(map(operator.mul, range(11), range(11))) # ➋
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> list(map(operator.mul, range(11), [2, 4, 8])) # ➌
[0, 4, 16]
>>> list(map(lambda a, b: (a, b), range(11), [2, 4, 8])) # ➍
[(0, 2), (1, 4), (2, 8)]
>>> import itertools
>>> list(itertools.starmap(operator.mul, enumerate('albatroz', 1))) # ➎
['a', 'll', 'bbb', 'aaaa', 'ttttt', 'rrrrrr', 'ooooooo', 'zzzzzzzz']
>>> sample = [5, 4, 2, 8, 7, 6, 3, 0, 9, 1]
>>> list(itertools.starmap(lambda a, b: b/a,
... enumerate(itertools.accumulate(sample), 1))) # ➏
[5.0, 4.5, 3.6666666666666665, 4.75, 5.2, 5.333333333333333,
5.0, 4.375, 4.888888888888889, 4.5]

❶ 从 1 开始,为单词中的字母编号。
❷ 从 0 到 10,计算各个整数的平方。
❸ 计算两个可迭代对象中对应位置上的两个元素之积,元素最少的那个可迭代对象到头后就停止。
❹ 作用等同于内置的 zip 函数。
❺ 从 1 开始,根据字母所在的位置,把字母重复相应的次数。
❻ 计算平均值。

合并多个可迭代对象的生成器函数

模块 函数 说明
itertools chain(it1, ..., itN)

先产出 it1 中的所有元素,然后产出 it2 中的
所有元素,以此类推,无缝连接在一起

itertools chain.from_iterable(it)

产出 it 生成的各个可迭代对象中的元素,一个
接一个,无缝连接在一起; it 应该产出可迭代
的元素,例如可迭代的对象列表

itertools

product(it1, ..., itN,
repeat=1)

计算笛卡儿积:从输入的各个可迭代对象中获
取元素,合并成由 N 个元素组成的元组,与嵌
套的 for 循环效果一样; repeat 指明重复处理
多少次输入的可迭代对象

内置 zip(it1, ..., itN)

并行从输入的各个可迭代对象中获取元素,产
出由 N 个元素组成的元组,只要有一个可迭代
的对象到头了,就默默地停止

itertools

zip_longest(it1, ...,
itN, fillvalue=None)

并行从输入的各个可迭代对象中获取元素,产
出由 N 个元素组成的元组,等到最长的可迭代
对象到头后才停止,空缺的值使用 fillvalue
填充

演示用于合并的生成器函数

>>> list(itertools.chain('ABC', range(2))) # ➊
['A', 'B', 'C', 0, 1]
>>> list(itertools.chain(enumerate('ABC'))) # ➋
[(0, 'A'), (1, 'B'), (2, 'C')]
>>> list(itertools.chain.from_iterable(enumerate('ABC'))) # ➌
[0, 'A', 1, 'B', 2, 'C']
>>> list(zip('ABC', range(5))) # ➍
[('A', 0), ('B', 1), ('C', 2)]
>>> list(zip('ABC', range(5), [10, 20, 30, 40])) # ➎
[('A', 0, 10), ('B', 1, 20), ('C', 2, 30)]
>>> list(itertools.zip_longest('ABC', range(5))) # ➏
[('A', 0), ('B', 1), ('C', 2), (None, 3), (None, 4)]
>>> list(itertools.zip_longest('ABC', range(5), fillvalue='?')) # ➐
[('A', 0), ('B', 1), ('C', 2), ('?', 3), ('?', 4)]

❶ 调用 chain 函数时通常传入两个或更多个可迭代对象。
❷ 如果只传入一个可迭代的对象,那么 chain 函数没什么用。
❸ 但是 chain.from_iterable 函数从可迭代的对象中获取每个元素,
然后按顺序把元素连接起来,前提是各个元素本身也是可迭代的对象。
❹ zip 常用于把两个可迭代的对象合并成一系列由两个元素组成的元
组。
❺ zip 可以并行处理任意数量个可迭代的对象,不过只要有一个可迭代
的对象到头了,生成器就停止。
❻ itertools.zip_longest 函数的作用与 zip 类似,不过输入的所有
可迭代对象都会处理到头,如果需要会填充 None。
❼ fillvalue 关键字参数用于指定填充的值。

itertools.product 生成器是计算笛卡儿积的惰性方式;以惰性方式计算笛卡儿积。

示例itertools.product 函数的用法。

>>> list(itertools.product('ABC', range(2))) # ➊
[('A', 0), ('A', 1), ('B', 0), ('B', 1), ('C', 0), ('C', 1)]
>>> suits = 'spades hearts diamonds clubs'.split()
>>> list(itertools.product('AK', suits)) # ➋
[('A', 'spades'), ('A', 'hearts'), ('A', 'diamonds'), ('A', 'clubs'),
('K', 'spades'), ('K', 'hearts'), ('K', 'diamonds'), ('K', 'clubs')]
>>> list(itertools.product('ABC')) # ➌
[('A',), ('B',), ('C',)]
>>> list(itertools.product('ABC', repeat=2)) # ➍
[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'),
('B', 'C'), ('C', 'A'), ('C', 'B'), ('C', 'C')]
>>> list(itertools.product(range(2), repeat=3))
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0),
(1, 0, 1), (1, 1, 0), (1, 1, 1)]
>>> rows = itertools.product('AB', range(2), repeat=2)
>>> for row in rows: print(row)
...
('A', 0, 'A', 0)
('A', 0, 'A', 1)
('A', 0, 'B', 0)
('A', 0, 'B', 1)
('A', 1, 'A', 0)
('A', 1, 'A', 1)
('A', 1, 'B', 0)
('A', 1, 'B', 1)
('B', 0, 'A', 0)
('B', 0, 'A', 1)
('B', 0, 'B', 0)
('B', 0, 'B', 1)
('B', 1, 'A', 0)
('B', 1, 'A', 1)
('B', 1, 'B', 0)
('B', 1, 'B', 1)

❶ 三个字符的字符串与两个整数的值域得到的笛卡儿积是六个元组
(因为 3 * 2 等于 6)。
❷ 两张牌('AK')与四种花色得到的笛卡儿积是八个元组。
❸ 如果传入一个可迭代的对象,product 函数产出的是一系列只有一
个元素的元组,不是特别有用。
❹ repeat=N 关键字参数告诉 product 函数重复 N 次处理输入的各个
可迭代对象。

把输入的各个元素扩展成多个输出元素的生成器函数

模块 函数 说明
itertools combinations(it, out_len)

把 it 产出的 out_len 个元素组合在
一起,然后产出

itertools

combinations_with_replacement(it,
out_len)

把 it 产出的 out_len 个元素组合在
一起,然后产出,包含相同元素的
组合

itertools count(start=0, step=1)

从 start 开始不断产出数字,按
step 指定的步幅增加

itertools cycle(it)

从 it 中产出各个元素,存储各个元
素的副本,然后按顺序重复不断地
产出各个元素

itertools permutations(it, out_len=None)

把 out_len 个 it 产出的元素排列在
一起,然后产出这些排列; out_len
的默认值等于 len(list(it))

itertools repeat(item, [times])

重复不断地产出指定的元素,除非
提供 times ,指定次数

itertools 模块中的 count 和 repeat 函数返回的生成器“无中生有”:这两个函数都不接受可迭代的对象作为输入。
cycle 生成器会备份输入的可迭代对象,然后重复产出对象中的元素。

count、repeat 和 cycle的用法。

>>> ct = itertools.count() # ➊
>>> next(ct) # ➋
0
>>> next(ct), next(ct), next(ct) # ➌
(1, 2, 3)
>>> list(itertools.islice(itertools.count(1, .3), 3)) # ➍
[1, 1.3, 1.6]
>>> cy = itertools.cycle('ABC') # ➎
>>> next(cy)
'A'
>>> list(itertools.islice(cy, 7)) # ➏
['B', 'C', 'A', 'B', 'C', 'A', 'B']
>>> rp = itertools.repeat(7) # ➐
>>> next(rp), next(rp)
(7, 7)
>>> list(itertools.repeat(8, 4)) # ➑
[8, 8, 8, 8]
>>> list(map(operator.mul, range(11), itertools.repeat(5))) # ➒
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]

❶ 使用 count 函数构建 ct 生成器。
❷ 获取 ct 中的第一个元素。
❸ 不能使用 ct 构建列表,因为 ct 是无穷的,所以我获取接下来的 3
个元素。
❹ 如果使用 islice 或 takewhile 函数做了限制,可以从 count 生成
器中构建列表。
❺ 使用 'ABC' 构建一个 cycle 生成器,然后获取第一个元素
——'A'。
❻ 只有受到 islice 函数的限制,才能构建列表;这里获取接下来的 7
个元素。
❼ 构建一个 repeat 生成器,始终产出数字 7。
❽ 传入 times 参数可以限制 repeat 生成器生成的元素数量:这里会
生成 4 次数字 8。
❾ repeat 函数的常见用途:为 map 函数提供固定参数,这里提供的是
乘数 5。

组合学生成器函数会从输入的各个元素中产出多个值

>>> list(itertools.combinations('ABC', 2)) # ➊
[('A', 'B'), ('A', 'C'), ('B', 'C')]
>>> list(itertools.combinations_with_replacement('ABC', 2)) # ➋
[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'B'), ('B', 'C'), ('C', 'C')]
>>> list(itertools.permutations('ABC', 2)) # ➌
[('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
>>> list(itertools.product('ABC', repeat=2)) # ➍
[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'), ('B', 'C'),
('C', 'A'), ('C', 'B'), ('C', 'C')]

❶ 'ABC' 中每两个元素(len()==2)的各种组合;在生成的元组中,
元素的顺序无关紧要(可以视作集合)。
❷ 'ABC' 中每两个元素(len()==2)的各种组合,包括相同元素的组
合。
❸ 'ABC' 中每两个元素(len()==2)的各种排列;在生成的元组中,
元素的顺序有重要意义。
❹ 'ABC' 和 'ABC'(repeat=2 的效果)的笛卡儿积。

用于重新排列元素的生成器函数

模块 函数 说明
itertools groupby(it,key=None)

产出由两个元素组成的元素,形式为 (key,
group) ,其中 key 是分组标准, group 是生成器,
用于产出分组里的元素

内置 reversed(seq)

从后向前,倒序产出 seq 中的元素; seq 必须是序
列,或者是实现了 __reversed__ 特殊方法的对象

itertools tee(it, n=2)

产出一个由 n 个生成器组成的元组,每个生成器
用于单独产出输入的可迭代对象中的元素

itertools.groupby 函数的用法

>>> list(itertools.groupby('LLLLAAGGG')) # ➊
[('L', <itertools._grouper object at 0x102227cc0>),
('A', <itertools._grouper object at 0x102227b38>),
('G', <itertools._grouper object at 0x102227b70>)]
>>> for char, group in itertools.groupby('LLLLAAAGG'): # ➋
... print(char, '->', list(group))
...
L -> ['L', 'L', 'L', 'L']
A -> ['A', 'A',]
G -> ['G', 'G', 'G']
>>> animals = ['duck', 'eagle', 'rat', 'giraffe', 'bear',
... 'bat', 'dolphin', 'shark', 'lion']
>>> animals.sort(key=len) # ➌
>>> animals
['rat', 'bat', 'duck', 'bear', 'lion', 'eagle', 'shark',
'giraffe', 'dolphin']
>>> for length, group in itertools.groupby(animals, len): # ➍
... print(length, '->', list(group))
...
3 -> ['rat', 'bat']
4 -> ['duck', 'bear', 'lion']
5 -> ['eagle', 'shark']
7 -> ['giraffe', 'dolphin']
>>> for length, group in itertools.groupby(reversed(animals), len): # ➎
... print(length, '->', list(group))
...
7 -> ['dolphin', 'giraffe']
5 -> ['shark', 'eagle']
4 -> ['lion', 'bear', 'duck']
3 -> ['bat', 'rat']
>>>

❶ groupby 函数产出 (key, group_generator) 这种形式的元组。
❷ 处理 groupby 函数返回的生成器要嵌套迭代:这里在外层使用 for
循环,内层使用列表推导。
❸ 为了使用 groupby 函数,要排序输入;这里按照单词的长度排序。
❹ 再次遍历 key 和 group 值对,把 key 显示出来,并把 group 扩展成
列表。
❺ 这里使用 reverse 生成器从右向左迭代 animals。

最后一个生成器函数是 iterator.tee,这个函数只有一个
作用:从输入的一个可迭代对象中产出多个生成器,每个生成器都可以产出输入的各个元素。产出的生成器可以单独使用

>>> list(itertools.tee('ABC'))
[<itertools._tee object at 0x10222abc8>, <itertools._tee object at 0x10222ac08>]
>>> g1, g2 = itertools.tee('ABC')
>>> next(g1)
'A'
>>> next(g2)
'A'
>>> next(g2)
'B'
>>> list(g1)
['B', 'C']
>>> list(g2)
['C']
>>> list(zip(*itertools.tee('ABC')))
[('A', 'A'), ('B', 'B'), ('C', 'C')]

Python 3.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]
>>> def chain(*iterables):
... for i in iterables:
... yield from i
...
>>> list(chain(s, t))
['A', 'B', 'C', 0, 1, 2]

可以看出,yield from i 完全代替了内层的 for 循环。在这个示例中使用 yield from 是对的,而且代码读起来更顺畅,不过感觉更像是语法糖。
除了代替循环之外,yield from 还会创建通道,把内层生成器直接与外层生成器的客户端联系起来。把生成器当成协程使用时,这个通道特别重要,不仅能为客户端代码生成值,还能使用客户端代码提供的值。

流畅的python 14章可迭代的对象、迭代器 和生成器的更多相关文章

  1. Python核心编程的四大神兽:迭代器、生成器、闭包以及装饰器

      生成器 生成器是生成一个值的特殊函数,它具有这样的特点:第一次执行该函数时,先从头按顺序执行,在碰到yield关键字时该函数会暂停执行该函数后续的代码,并且返回一个值:在下一次调用该函数执行时,程 ...

  2. 流畅的python第十四章可迭代的对象,迭代器和生成器学习记录

    在python中,所有集合都可以迭代,在python语言内部,迭代器用于支持 for循环 构建和扩展集合类型 逐行遍历文本文件 列表推导,字典推导和集合推导 元组拆包 调用函数时,使用*拆包实参 本章 ...

  3. 流畅的python第二章序列构成的数组学习记录

    python内置序列类型概览 列表推导和生成器表达式 列表推导是构建列表的快捷方式,而生成器表达式可以用来创建其他任何类型的序列 列表推导的示例 >>>test = [i*2 for ...

  4. python编程系列---可迭代对象,迭代器和生成器详解

    一.三者在代码上的特征 1.有__iter__方法的对象就是可迭代类(对象) 2.有__iter__方法,__next()方法的对象就是迭代器3.生成器 == 函数+yield 生成器属于迭代器, 迭 ...

  5. Python开发【第五篇】迭代器、生成器、递归函数、二分法

    阅读目录 一.迭代器 1. 迭代的概念 #迭代器即迭代的工具(自定义的函数),那什么是迭代呢? #迭代:指一个重复的过程,每次重复都可以称之为一次迭代,并且每一次重复的结果是下一个迭代的初始值(例如: ...

  6. Python 入门基础11 --函数基础4 迭代器、生成器、枚举类型

    今日目录: 1.迭代器 2.可迭代对象 3.迭代器对象 4.for循环迭代器 5.生成器 6.枚举对象 一.迭代器: 循环反馈的容器(集合类型) 每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的 ...

  7. Python——五分钟带你弄懂迭代器与生成器,夯实代码能力

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是周一Python专题,给大家带来的是Python当中生成器和迭代器的使用. 我当初第一次学到迭代器和生成器的时候,并没有太在意,只是觉 ...

  8. [流畅的Python]第一章数据模型

    这些来自同一家出版社的动物书 像是计算机科学界一盏盏指路明灯 余幼时 初试读 学浅 以为之晦涩难懂 像是老学究咬文嚼字 现在看起来还有些有趣 其实理工男大多都很有趣 这一章介绍了 怎么样去视线一个带有 ...

  9. Head First Python 1-4章学习感悟

    一.学习知识总结 (1)迭代         range(起始值,结束值,步长):包含起始值,不包含结束值,步长为正数 (2)导入模块 from datetime import datetime (3 ...

随机推荐

  1. iptables详细教程:基础、架构、清空规则、追加规则、应用实例(转)

    iptables防火墙可以用于创建过滤(filter)与NAT规则.所有Linux发行版都能使用iptables,因此理解如何配置iptables将会帮助你更有效地管理Linux防火墙.如果你是第一次 ...

  2. JVM-String常量池与运行时常量池

    Start with JVM 周志明先生著-<深入理解Java虚拟机>,书买回来好几天了,但是最近才准备开始搞一搞了(哭瞎…..).首先是第一章的Java以及JVM发展历史,大概知道了现行 ...

  3. oozie搭建手册

    环境准备 mave安装 1.下载并上传maven,然后解压 tar -zxvf apache-maven-3.3.9-bin.tar.gz -C /home 2.配置环境变量 vi /etc/prof ...

  4. TCP/IP详解读书笔记:ARP-地址解析协议

    地址解析为两种不同的地址形式提供映射:32bit的IP和数据链路层使用的任何类型的地址. 当一台主机把以太网数据帧发送到位于同一局域网的另一台主机,是根据48bit的以太网地址而不是IP地址.设备驱动 ...

  5. hihoCoder #1320 : 压缩字符串 区间dp

    /** 题目:hihoCoder #1320 : 压缩字符串 链接:https://hihocoder.com/problemset/problem/1320 描述 小Hi希望压缩一个只包含大写字母' ...

  6. [转]Linux系统下yum和apt-get的区别

    一般来说著名的linux系统基本上分两大类: 1.RedHat系列:Redhat.Centos.Fedora等 2.Debian系列:Debian.Ubuntu等 RedHat 系列 1 常见的安装包 ...

  7. tomcat 内存溢出原因分析及解决

    一.错误提示:java.lang.OutOfMemoryError: Java heap space [原因分析] tomcat默认可以使用内存为128MB,在较大型的应用项目中不足以满足运行要求,在 ...

  8. python通过日志分析加入黑名单

    监控nginx日志,若有人攻击,则加入黑名单,操作步骤如下:1.读取日志文件2.分隔文件,取出ip3.将取出的ip放入list,然后判读ip的次数4.若超过设定的次数,则加入黑名单 日志信息如下: 1 ...

  9. 第二百三十八节,Bootstrap输入框和导航组件

    Bootstrap输入框和导航组件 学习要点: 1.输入框组件 2.导航组件 3.导航条组件 本节课我们主要学习一下Bootstrap的两个个组件功能:输入框组件和导航导航条组件. 一.输入框组件 文 ...

  10. [翻译]在ASP.NET Web API中通过OData支持查询和分页

    OData可以通过形如http://localhost/Products?$orderby=Name这样的QueryString传递查询条件.排序等.你可以在任何Web API Controller中 ...