0%

Python之迭代器、生成器、Iteration Protocol迭代协议

这里介绍Python的迭代器、生成器、Iteration Protocol迭代协议

abstract.png

迭代器

迭代器是一个会记住遍历位置的对象。一方面,当迭代器中的元素被访问过后,就无法再次访问之前的元素。因为其是单向遍历的;另一方面,迭代器从第一个元素开始访问,当所有的元素都被访问完,就不能再次使用。具体地,可使用iter()函数通过 可迭代对象 创建 迭代器对象。然后利用for循环、next函数等方法实现遍历访问

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
28
29
30
31
32
33
34
35
36
37
38
39
from typing import Iterator

print("\n-------------访问迭代器方式1:for循环------------------------")
names = ["Bob","Lucy", "Tony" , "Amy"]
# iter()函数:通过 可迭代对象 创建迭代器对象
iterator1 = iter(names)
print(f"iterator1 : {iterator1}")
print(f"iterator1 类型为迭代器: {isinstance(iterator1, Iterator)}")

# 访问迭代器方式1:for循环
for name in iterator1:
print(f"name -->> {name}")


print("\n-------------访问迭代器方式2:next函数------------------------")
nums = (996,520, 1314, 69)
iterator2 = iter(nums)
print(f"iterator2 : {iterator2}")
print(f"iterator2 类型为迭代器: {isinstance(iterator2, Iterator)}")

# 访问迭代器方式2:next()函数
while True:
try:
print(f"num -->> { next(iterator2) }")
except StopIteration:
# 当迭代器中无元素可用时,将会抛出StopIteration异常
print("全部迭代结束")
break


print("\n------------------访问迭代器方式3:直接将其转化为可迭代对象--------------------")
ages = [28,27,26,11]
iterator3 = iter(ages)
print(f"iterator3 : {iterator3}")
print(f"iterator3 类型为迭代器: {isinstance(iterator3, Iterator)}")

# 访问迭代器方式3:直接将其转化为可迭代对象
ages_tuple =tuple(ages)
print(f"ages tuple : {ages_tuple}")

figure 1.png

生成器

生成器是一种特殊的迭代器。其有两种的创建方式:通过 生成器表达式或生成器函数 来创建。既然生成器也是迭代器的一种,自然也可以通过 for循环、next()函数、直接转换为可迭代对象 来访问

生成器表达式

所谓的生成器表达式,就是使用圆括号版本推导式,其返回的是一个生成器对象

1
2
3
4
5
6
7
8
9
10
11
12
from typing import Iterator, Generator

title = "生成器表达式"
print(f"------------ {title} -------------------")

#生成器的创建方式1:生成器表达式
g1 = (x**2 for x in range(5))
print(f"type(g1) :", type(g1))
print(f"g1 类型为生成器: {isinstance(g1, Generator)}")
print(f"g1 类型为迭代器: {isinstance(g1, Iterator)}")
for num in g1:
print(f"num->> {num}")

figure 2.png

生成器函数

Python中,包含 yield语句 的函数被称为生成器函数。其是一种特殊的函数。特殊点在于:

  1. 调用生成器凾数后,会直接返回一个新的生成器对象,但并没有执行生成器函数的函数体
  2. 对生成器对象每次通过next()函数遍历时,才会真正执行生成器函数中的函数体。具体执行流程:
    • yield语句用于产生返回值,同时函数立即返回;
    • 当下一次遍历时,其会从上次返回的yield语句处继续往下执行
  3. 当生成器函数中的函数体全部执行完毕后,会抛出StopIteration异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from typing import Iterator, Generator

title = "生成器函数"
print(f"------------ {title} -------------------")

def my_generator():
print("执行函数体:Hello One")
yield 111
print("执行函数体:Hello Two'")
yield 2
print("执行函数体:Hello Three'")
yield 3392

g2 = my_generator()
print(f"type(g2):",type(g2))
print(f"g2 类型为生成器: {isinstance(g2, Generator)}")
print(f"g2 类型为迭代器: {isinstance(g2, Iterator)}")
while True:
try:
print(f"g2 res:{next(g2)}")
except StopIteration:
print("生成器g2迭代结束")
break

figure 3.png

由于生成器函数的惰性计算特性,故非常适合用于大量数据的场景。这里展示如何通过生成器函数来计算斐波那契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from typing import Iterator, Generator

title = "斐波那契数列的生成器函数"
print(f"------------ {title} -------------------")

def fib_num_generator(max_count):
"""
斐波那契数列的生成器函数
max_count:遍历的最大次数限制100
"""
count =0
a,b=0,1
while count < max_count:
count = count +1
a, b = b, a+b
yield a

g3 = fib_num_generator(7)
print(f"type(g3):",type(g3))
print(f"g3 类型为生成器: {isinstance(g3, Generator)}")
print(f"g3 类型为迭代器: {isinstance(g3, Iterator)}")
res = tuple(g3)
print(f"res : {res}")

figure 4.png

Iteration Protocol 迭代协议

传统实现

对于一个自定义对象而言,如果想将其视作为一个可迭代对象。就必须实现Iteration Protocol迭代协议。具体地:

  • 自定义对象需要实现 __iter__() 方法,使其可以被视作为一个可迭代对象。具体地,该方法会返回一个迭代器对象
  • 对于迭代器对象而言,其一方面,需要实现 __next__() 方法,用于获取迭代器对象中的下一个元素;另一方面,通常还需要实现 __iter__() 方法,其会返回迭代器对象自身。此举是为了对 迭代器对象自身 进行迭代

通过下述例子不难看出,可迭代对象 和 迭代器对象 显然是两个对象。通常我们进行实现时,也是提供两个对象。强烈不推荐将两个对象合二为一,即在 可迭代对象中去实现 __next__() 方法

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
title = "迭代协议"
print(f"------------ {title} -------------------")

class AddressBook:
"""
通讯录
"""
def __init__(self):
self.list = []

def add_people_info(self,name,tel_no):
people_info = (name, tel_no)
self.list.append( people_info )

def __iter__(self):
"""
返回迭代器
"""
return AddressBookIterator(self)

class AddressBookIterator:
"""
通讯录迭代器
"""
def __init__(self, address_book):
self.address_book_list = address_book.list
self.current = 0

def __next__(self):
if self.current >= len(self.address_book_list) :
raise StopIteration

people_info = self.address_book_list[ self.current ]
self.current += 1
return people_info

def __iter__(self):
"""
返回迭代器
"""
return self

my_address_book = AddressBook()
my_address_book.add_people_info("Aaron", 110)
my_address_book.add_people_info("Tom", 120)
my_address_book.add_people_info("Davlid", 114)
my_address_book.add_people_info("Lucy", 119)


print("-------------------- 使用while循环 --------------------------")
# 调用iter()方法时: Python会自动调用自定义对象的__iter__()方法 来获取对象的迭代器对象
my_address_book_iterator1 = iter( my_address_book )
while True:
try:
# 调用next()方法时: Python会自动调用迭代器对象的__next__()方法 来获取迭代器对象中的下一个值
people_info = next( my_address_book_iterator1 )
print(f"people info : {people_info}")
except StopIteration:
break

print("-------------------- 使用for循环 --------------------------")
# for 循环的工作原理:
# 使用 for 循环遍历某个可迭代对象时,
# 本质上是,先调用iter()方法获取该可迭代对象的迭代器,然后再不断对迭代器调用next()方法从迭代器中获取值
for people_info in my_address_book:
print(f"people info ---->>>> {people_info}")

由于我们在迭代器AddressBookIterator中实现了 __iter__() 方法,其会返回迭代器自身。故我们就可以对 迭代器对象 使用循环迭代了

1
2
3
4
5
6
7

print("-------------------- 对迭代器进行迭代循环 --------------------------")
# 通常,自定义的迭代器还应该实现__iter__() 方法,其会返回迭代器自身
# 这样,我们就可以对 迭代器对象 使用循环迭代了
my_address_book_iterator2 = iter( my_address_book )
for people_info in my_address_book_iterator2:
print(f"PEOPLE INFO ---->>>> {people_info}")

figure 5.png

通过生成器来实现迭代协议

对于一个自定义对象而言,如果想将其视作为一个可迭代对象。我们在实现它的__iter__()方法时,还可以返回一个生成器。这样就避免了我们显式地提供一个迭代器对象了

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
title = "通过生成器来实现迭代器"
print(f"------------ {title} -------------------")

# 通过生成器来实现迭代器
class AddressBook2:
"""
通讯录
"""
def __init__(self):
self.list = []

def add_people_info(self,name,tel_no):
people_info = (name, tel_no)
self.list.append( people_info )

def __iter__(self):
"""
返回生成器
"""
for people_info in self.list:
yield people_info

my_address_book = AddressBook2()
my_address_book.add_people_info("老王", 52110)
my_address_book.add_people_info("老李", 52120)
my_address_book.add_people_info("张三", 52114)
my_address_book.add_people_info("李四", 52119)

print("\n-------------------- 使用while循环 --------------------------")
# 调用iter()方法时: Python会自动调用自定义对象的__iter__()方法 来获取对象的生成器对象
my_address_book_generator = iter( my_address_book )
while True:
try:
# 对生成器对象每次通过next()函数遍历时,才会真正执行生成器函数中的函数体
people_info = next( my_address_book_generator )
print(f"people info : {people_info}")
except StopIteration:
break


print("\n-------------------- 使用for循环 --------------------------")
# 先调用iter()方法获取该可迭代对象的生成器对象,然后不断对生成器对象调用next()方法,来真正执行生成器函数中的函数体
for people_info in my_address_book:
print(f"people info ---->>>> {people_info}")

print("\n-------------------- 对生成器进行迭代循环 --------------------------")
g1 = iter( my_address_book )
g2 = iter( g1 )
print(f"g1 : {g1}")
print(f"g2 : {g2}")
print(f"g1 is g2 : {g1 is g2}")
# 从上可以看出,g1和g2是同一个对象。换言之,对生成器对象使用iter()会返回生成器对象自身
# 这样,我们就可以对 生成器对象 使用循环迭代了
g3 = iter( my_address_book )
for people_info in g3:
print(f"PEOPLE INFO ---->>>> {people_info}")

figure 6.png

Note

当调用iter()方法时,Python完整的逻辑为:

  1. Python 会先检查对象是否实现了__iter__()方法。如果实现了就调用__iter__()方法来获取迭代器对象
  2. 如果没有实现__iter__()方法,但是实现了__getitem__()方法。Python会自动创建一个迭代器,尝试按从索引0开始按顺序获取元素
  3. 如果尝试失败,Python 抛异常。提示该对象不可迭代

参考文献

  1. Python编程·第3版:从入门到实践 Eric Matthes著
  2. Python基础教程·第3版 Magnus Lie Hetland著
  3. 流畅的Python·第1版 Luciano Ramalho著
请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝