Python 高级4

yield 生成器

定义一个生成器函数生产某个范围内浮点数

1
2
3
4
5
6
7
def frange(start, end, increment):
while start < end:
yield start
start += increment

for x in frange(1, 4, 0.5):
print(x)

一个生成器函数主要特征是它只会回应在迭代中使用到的 next 操作。 一旦生成器函数返回退出,迭代终止。我们在迭代中通常使用的for语句会自动处理这些细节,所以你无需担心。

1
2
3
4
5
6
7
8
9
def countdown(n):
while n > 0:
yield n
n -=1

c = countdown(3)
print(next(c)) # 3
print(next(c)) # 2
print(next(c)) # 1

反向迭代

自带的 reversed 实现反向迭代

1
2
3
a = [1, 2, 3, 4]
for x in reversed(a):
print(x) # 4321

也可以使用 __reversed__ 来实现自定义的反向迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Countdown:
def __init__(self, size):
self.size = size

def __iter__(self):
while self.size >= 0:
yield self.size
self.size -= 1

def __reversed__(self):
n = 0
while n <= self.size:
yield n
n += 1


if __name__ == '__main__':
# 调用 __iter__ 方法
for c1 in Countdown(5):
print(c1) # 543210

# 调用 __reversed__ 方法
for c2 in reversed(Countdown(5)):
print(c2) # 012345

有外部状态的生成器

下面代码的功能是输出文件内容,如果内容有某个关键内容,那么就打印包含该行数据在内的前三行历史数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from collections import deque


class linehistory:
def __init__(self, file, history_size=3):
# 文件对象
self.file = file
# 历史数据队列
self.history = deque(maxlen=history_size)

def __iter__(self):
"""
每次返回文件内容,并将数据存到队列中
:return:
"""
for index, value in enumerate(self.file, 1):
self.history.append((index, value))
yield value


if __name__ == '__main__':
with open("envs.py", encoding="utf-8") as f:
content = linehistory(f)
# 迭代文件,如果文件文件中包含 SECRET_KEY,那么就打印前三行信息
for c in content:
if "SECRET_KEY" in c:
print(content.history)

迭代器切片

标准的切片不能对迭代器进行切片,可以使用 itertools 中的 islice 方法

1
2
3
4
5
6
7
8
9
10
11
def count(n):
while True:
yield n
n += 1

a = count(0)
# a[10:20] # 报错

from itertools import islice
for x in islice(a, 10, 20):
print(x) # 10...19

迭代器和生成器不能使用标准的切片操作,因为它们的长度事先我们并不知道。islice 通过遍历并丢弃直到切片开始索引位置的所有元素。 然后才开始一个个的返回元素,并直到切片结束索引位置。

跳过可迭代对象

可以使用 dropwhile 跳过返回 flase 之前的元素,然后返回后面的所有元素。

1
2
3
4
5
6
7
8
a = [1,2,3,11,20,34,12,15,16,17,10,99,100]

from itertools import dropwhile

# 并不是丢弃小于 30 的元素,而是丢失函数中返回第一个 false 之前的元素,然后返回后面的所有元素。
# 此功能在去除文件开头的某些注释,而不想去除代码中的注释有帮助
for z in dropwhile(lambda x: not x > 30, a):
print(z) # 34,12,15,16,17,10,99,100

知道了要跳过的元素的序号的话,那么可以使用 itertools.islice() 来代替

1
2
3
4
5
6
7
a = [1,2,3,11,20,34,12,15,16,17,10,99,100]

from itertools import islice

# (a, 3, None) 相当于 a[3:]。同理,(a, None, 3) 相当于 a[:3]
for x in islice(a, 3, None):
print(x) # 11,20,34,12,15,16,17,10,99,100

排列组合

permutations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
item = ["a", "b", "c"]

from itertools import permutations
# 同一个元素不允许重复出现
for x in permutations(item):
print(x)
"""
('a', 'b', 'c')
('a', 'c', 'b')
('b', 'a', 'c')
('b', 'c', 'a')
('c', 'a', 'b')
('c', 'b', 'a')
"""

还可以指定元素的个数,例如 permutations(item, 2)

zip_longest, zip

zip 可以同时迭代多个序列,每次分别从一个序列中取一个元素。zip 一旦其中某个序列到底结尾,迭代宣告结束,因此迭代长度跟参数中最短序列长度一致。 而 zip_longest 则不会

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
a = ["a", "b", "c", "d"]
b = [1,2,3]

for x in zip(a, b):
print(x)
"""
('a', 1)
('b', 2)
('c', 3)
"""

from itertools import zip_longest
for x in zip_longest(a, b, fillvalue="None"):
print(x)
"""
('a', 1)
('b', 2)
('c', 3)
('d', 'None')
"""

一次迭代多个可迭代对象

1
2
3
4
5
6
7
8
a = ["a", "b", "c", "d"]
b = [1,2,3]

from itertools import chain

# 使用 chain 比将两个可迭代对象进行合并然后再迭代效率高很多
for x in chain(a, b):
print(x) # abcd123

合并有序迭代

heapq 中的 merge 方法可以将两个有序迭代对象合并为一个大的有序可迭代对象

1
2
3
4
5
6
7
8
import heapq

a = [10, 20, 30, 40] # a 有序
b = [15, 16, 22, 39, 41] # b 有序

# 最后合成一个有序的可迭代对象
for x in heapq.merge(a, b):
print(x) # 10 15 16 20 22 30 39 40 41