1、迭代协议
迭代就是可以使用循环将数据挨个挨个取出来,这个好理解是吧,比如,咱们常见的对一个列表进行迭代:
for i in [1, 2, 3]:
print(i)
结果不用讲肯定是挨着取出列表里面的数字了。
那列表里面究竟是实现了什么协议,或者说一个对象实现什么魔法函数就可以迭代呢,这就是迭代协议:__iter__
一个类只要实现了魔法函数 __iter__
就是可迭代的(Iterable
),但是它还不是迭代器(Iterator
),品一下区别。
class IterTest:
def __iter__(self):
...
来验证一下:
from collections.abc import Iterable
from collections.abc import Iterator
print("是否可迭代:", isinstance(IterTest(), Iterable))
print("是否为迭代器:", isinstance(IterTest(), Iterator))
是否可迭代:True
是否为迭代器:False
你看实现了迭代协议,就是可迭代的,想起鸭子类型了吗。
2、迭代器和可迭代对象
我们现在知道一个对象只要实现了 __iter__
就是一个可迭代的对象,现在咱们来试试对一个可迭代对象使用 for 循环进行迭代,放个简单的列表进去看看:
class IterTest:
def __iter__(self):
return [1, 2, 3]
for i in IterTest():
print(i)
在 __iter__
函数里面返回一个列表,列表是一个可迭代的对象,但不是迭代器。
Traceback (most recent call last):
File "/tmp/pycharm_project_609/123.py", line 11, in
for i in IterTest():
TypeError: iter() returned non-iterator of type 'list'
运行报错了,说 iter 返回了一个不是迭代器的对象。说明在 __iter__
里面需要返回一个迭代器,对吧,其他的先不管,咱们放一个迭代器进去,保证程序跑起来不报错。
放一个生成器表达式进去试试:
class IterTest:
def __iter__(self):
return (i for i in range(3))
for i in IterTest():
print(i)
0
1
2
唉,这下对了,没报错,而且也能迭代出来了。
但是,此时仍然还不是一个迭代器,要实现迭代器,还必须要实现另外一个魔法函数:__next__
class IterTest:
def __iter__(self):
return (i for i in range(3))
def __next__(self):
...
验证一下
print("是否为迭代器:", isinstance(IterTest(), Iterator))
是否为迭代器: True
你看,实现 __next__
之后,就是一个迭代器了。那 __next__
应该怎么写,前面我们已经看到, __iter__
里面是不负责逻辑处理的,它只管返回,逻辑处理需要在 __next__
里面去做。
使用经典的斐波那契数列来举例:
class Fib:
def __init__(self, n):
self.a, self.b = 0, 1
self.n = n
# 返回迭代器对象本身
def __iter__(self):
return self
# 返回容器下一个元素
def __next__(self):
if self.n > 0:
self.a, self.b = self.b, self.a + self.b
self.n -= 1
return self.a
else:
raise StopIteration
这里面 n 是用来限制迭代次数的,不然这个循环将一直进行下去,直到宇宙的尽头,抛 StopIteration
异常会被 for
循环自动处理掉。
for i in Fib(10):
print(i)
1
2
3
5
8
13
21
34
55
这样我们就实现了一个简单的迭代器。
简单一句话总结一下:迭代器就是使对象可以进行 for 循环,它需要实现 __iter__
和 __next__
两个魔法函数。
有同学要说了,就这?不就用 for 循环嘛,搞这么复杂嘎哈,我为什么要用迭代器啊?
为什么要使用迭代器
节省资源消耗,迭代器并不会计算每一项的值,它只在你访问这些项的时候才计算,也就是说它保存的是一种计算方法,而不是计算的结果。能理解吗,相当于迭代器是鱼竿,而不是一池子的鱼,需要鱼的时候钓就行了,而不用把所有鱼都搬回家。
平时可能感受不到哈,当你需要计算一个非常大的数据时,你就能感受到了,这就是“惰性求值”的魅力。
你可以试试前面的斐波那契数列的列子,对比一个普通的列表,然后给一个很大的数字,区别就很明显了。
3、生成器
生成器也是一种迭代器,特殊的迭代器,它也可以用 for 循环来取值,但是大部分的情况下是使用 next() 函数进行取值。
前面我们讲生成器表达式已经见识过,这是一种便携的写生成器的方法:
my_gen = (i for i in range(10))
print(next(my_gen))
print(next(my_gen))
print(next(my_gen))
print(next(my_gen))
0
1
2
3
一般这么玩的哈。
前面讲的好多对象都是在类里面定义的,而生成器对象就不是在类里面了,而是在函数里面定义,在一个函数里面只要出现了 yield
它就不是普通函数,而是一个生成器。
def my_gen():
print("setp 1")
yield 1
print("setp 2")
yield 2
g = my_gen()
next(g)
next(g)
step 1
step 2
yield
的用途是让函数暂停,并保存对象状态在内存中,下次再使用 next
调用同一个对象时,又开始从之前暂停的位置开始执行,直到运行到下一个 yield
又暂停,如果后面没有 yield
了,则会抛 StopIteration
异常。
yield
和 return
都能返回数据,但是有区别,return
语句之后的代码是不执行的,而 yield
后面还可以执行。
有同学要问了,生成器函数里面能用 return
吗?好问题,不愧是你。
生成器里面是可以用 return
的,但是,return
后面的数据不会被返回。
举例:
def my_gen():
yield 1
yield 2
return 3
for i in my_gen():
print(i)
1
2
你看,3 并没有被返回,所以说生成器里面的 return
只是一个结束的标志,它不会把后面的值返回给调用者,这跟函数里面的 return
是不一样的。
4、总结
看完前面迭代器和生成器的内容,可能有些同学有点晕了,没关系,多看几遍,经常看,经常晕。
我们简单总结一下:
- 迭代器需要实现两个魔法函数:
__iter__
和__next__
; - 迭代器允许惰性求值,只有在请求下一个元素时迭代器对象才会去生成它,它保存的是一种生成数据的方法;
- 生成器是迭代器的一种更
Pythonic
的写法,可以在函数里面用yield
创建一个迭代器; - 生成器表达式是生成器的一种更加
Pythonic
的写法。