先放一张图来表示以下三者的关系:
1.生成器(generator)
为了更好的理解生成器,可以先把生成器和列表做一个比较。
理论上列表的长度是无限的,但是受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。而使用生成器也可以用来存储100万个元素。只不过生成器内部实际存储的 是一种算法,可以通过前面的元素推倒出后面的元素。这样可以大大节省内存空间。
列表可以通过列表生成式来创建,生成器可以有两种创建方式,一种是生成器表达式,一种是生成器函数(即带yield的函数,后面会讲到)。第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator:
>>> l = [x for x in range(10)]
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> type(l)
<class 'list'>
>>> g = (x for x in range(10))
>>> g
<generator object <genexpr> at 0x0000016DE5F3A410>
>>> type(g)
<class 'generator'>
可以看到列表生成式返回的是一个列表,而生成器表达式返回的是一个生成器。
那么如何获取生成器里面的元素的呢?如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值。
>>> next(g)
0
>>> next(g)
1
>>> next(g)
2
...
>>> next(g)
9
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
前面说过,generator保存的是算法,每次调用next(g),就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。当调用next()时,实际上next()函数调用了g对象的__next__()方法,即next(g)等价于g.__next__()。
当然,实际上很少用这种方式来获取元素,可以直接用for循环,因为generator也是可迭代对象。for循环内部实际上也是调用的是g.__next__()方法,只不过已经帮我们处理了异常。
>>> for x in g:
... print(x)
...
0
1
2
3
4
5
6
7
8
9
>>>
生成器的第二种创建方式是生成器函数,来看一个例子:
def test():
for i in range(10):
yield i
t = test()
print(type(t))
for j in t:
print(j, end=" ")
## 运行结果:
<class 'generator'>
0 1 2 3 4 5 6 7 8 9
如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator。generator函数在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield
语句处继续执行。
2.迭代器(iterator)和可迭代对象(iterable)
可以直接作用于for循环的对象都叫可迭代对象。包括生成器和迭代器(生成器是迭代器的一个特例),还有str,list,tuple,set,dict等。
注意:str,list,tuple,set,dict虽然是可迭代队对象,但并不是迭代器。不过可以使用iter()函数将他们变成迭代器。当调用iter()函数时,iter()函数实际上调用了iterable的__iter__()方法。
迭代器内部都实现了__iter__()方法和__next__()方法。
from collections import Iterator, Iterable
a = "abc"
b = [1,2,3]
aa = iter(a)
bb = b.__iter__()
print(isinstance(a, Iterable))
print(isinstance(a, Iterator))
print(type(aa))
print(type(bb))
补充:当用for作用于可迭代对象时,如list等,实际上是先调动list.__iter__()方法将list转换成迭代器,在调用迭代器的__next__()方法一次取出元素。
图片参考自:https://nvie.com/posts/iterators-vs-generators/