前言
文章是《流畅的Python》学习笔记与个人总结。
可迭代对象
序列可以迭代的原因:iter
函数
iter函数的作用:
- 检查对象是否实现了 iter 方法,如果实现了就调用它,获取一个迭代器。
- 如果没有实现 iter 方法,但是实现了 getitem 方法, Python 会创建一个迭代器,尝试按顺序(从索引 0 开始)获取元素。
- 如果尝试失败, Python 抛出 TypeError 异常,通常会提示“C object is not iterable”(C对象不可迭代),其中 C 是目标对象所属的类。、
要想让自定义的类型实现可以使用鸭子类型来实现可迭代:即实现__iter__
方法。
可迭代对象与迭代器的对比
迭代器内部实现了__next__
和__iter__
方法。
-
__next__
:返回下一个可用的元素,如果没有元素了,抛出topIteration
异常。 -
__iter__
: 返回 self,以便在应该使用可迭代对象的地方使用迭代器,例如在 for 循环中。在迭代器中
__iter__
方法和在可迭代对象内部的__iter__
方法实现的功能不同:
具体的
Iterable.__iter
__ 方法应该返回一个Iterator
实例。具体的Iterator
类必须实现__next__
方法。Iterator.__iter__
方法直接返回实例本身 。
迭代器使用
Python中的迭代器一定是可迭代对象,可迭代对象不一定是迭代器,因为迭代器还要实现__next__
方法。
通过iter()
方法构造一个迭代器,并使用next()
方法迭代,当抛出异常后表明已经迭代完所有元素,此时可以使用del
关键字释放对迭代器对象的引用:
>>> s = 'ABC'
>>> it = iter(s)
>>> while True:
... try:
... print(next(it))
... except StopIteration:
... del it
... break
...
A
B
C
生成器函数
生成器内部的__iter__
方法不再是返回实例本身,而是返回一个使用yield
关键字的迭代器,所以说生成器也是一个迭代器对象。
所以生成器也可以使用__next__
方法进行迭代或是使用循环,而且在使用next()
方法迭代完最后一个元素后也会抛出异常。
生成器是对迭代器的进一步优化:惰性思想的优化,在使用的时候构造返回,而迭代器讲究的是取出,是提前就构造好的,占用了内存空间的。
-
在函数中使用
yield
关键字构造一个生成器函数:>>> def gen_AB(): ... print('start') # 使用yield关键字构造生成器返回的内容(在使用到的时候才会构造,否则不会执行这行代码) ... yield 'A' ... print('continue') ... yield 'B' ... print('end.') ... # 因为生成器是一个迭代器对象所以我们可以使用循环迭代 >>> for c in gen_AB(): ... print('-->', c) ... start --> A continue --> B end. >>>
-
内置函数
re.finditer
函数是re.findall
函数的惰性版本,返回的不是列表,而是一个生成器 。
生成器表达式
生成器表达式很简单,只需要将列表推导式的[]
换成()
就可以了:
listor = [i for i in gen_test()]
# 不过你要保证gen_test()是一个可迭代对象
genor = (i for i in gen_test())
如果函数或构造方法只有一个参数,传入生成器表达式时不用写一对调用函
数的括号,再写一对括号围住生成器表达式,只写一对括号就行了 :
>>> l = [1,2,3]
# 不用这样写sum((n*2 for n in l))
>>> sum(n*2 for n in l)
12
>>>
标准库中的生成器函数
用于过滤的生成器函数
模块 | 函数 | 说明 |
---|---|---|
itertools | compress(data, selectors) | 创建一个迭代器,它返回 data 中经 selectors 真值测试为 True 的元素。迭代器在两者较短的长度处停止。 |
itertools | dropwhile(predicate, it) | 处理 it,跳过 predicate 的计算结果为真值的元素,直到遇到false的值停止然后 产出剩下的各个元素(不再进一步检查) |
(内置) | 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']
用于映射的生成器函数
模块 | 函数 | 说明 |
---|---|---|
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 |
>>> 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]
>>> 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 关键字参数用于指定填充的值 。
>>> 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,指定次数 |
>>> 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 个生成器组成的元组,每个生成器用于单独产出输入的可迭代对象中的元素 |
>>> 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']
>>> 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')]
深入分析iter函数
iter函数可以传入两个参数:
- 可迭代对象
- 哨符:标记,当调用的对象返回这个值时,触发迭代器抛出异常,所以不会返回标记的值。
例:追行读取文件,直到遇到空行或者到达文件末尾为止:
with open('mydata.txt') as fp:
for line in iter(fp.readline, '\n'):
process_line(line)