生成器是python中非常有用特性,而且十分特殊,特殊到以前学过的语言都没有这种功能,身边的一些学其他语言的开发朋友也都不清楚生成器是什么。
所以在这篇文章中将尽可能详细的解释下生成器是什么,迭代器是什么
可迭代对象
在说生成器之前我们必须得提可迭代对象。可迭代对象是什么,顾名思义迭代器就是支持迭代操作的对象。在python3如列表,字典,字符串都是可迭代对象,简单来说就是任何实现了__next__方法的容器都是可迭代对象,而可迭代对象可以转换成迭代器。
迭代器甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
看了下面的代码应该会清楚很多
In [7]: a = [1,2,3]
In [8]: b = iter(a)
In [9]: a
Out[9]: [1, 2, 3]
In [10]: b
Out[10]: <list_iterator at 0x10c6fa590>
In [11]: next(b)
Out[11]: 1
In [12]: next(b)
Out[12]: 2
In [13]: next(b)
Out[13]: 3
In [14]: type(a)
Out[14]: list
In [15]: type(b)
Out[15]: list_iterator
通过上面这段代码可以知道可迭代对象实现了__iter__方法,所有实现了__iter__方法的容器都可以转换成迭代器:如list_iterator
,字典的话就是dict_keyiterator
。迭代器也不是固定的数据类型,不同的容器对应着不同的迭代器。
迭代器
从上面结果中可以得知在python3中任何实现了__iter__和__next__方法的对象都是迭代器。
迭代器的原理有点类似于将容器结合了链表。在执行循环的时候不会把对象一次性全部都加载到内存中,而是要等需要用的时候通过next方法来获取下一个元素,所以for循环实际上就是在不停的调用迭代器的next方法。如果容器中没有更多元素了,迭代器就会抛出异常
In [11]: next(b)
Out[11]: 1
In [12]: next(b)
Out[12]: 2
In [13]: next(b)
Out[13]: 3
In [14]: next(b)
StopIteration Traceback (most recent call last)
当然我们也可以将它变成无限序列的迭代器
In [27]: from itertools import cycle
In [28]: a = [1,2,3]
In [29]: c = cycle(a)
In [31]: next(c)
Out[31]: 1
In [32]: next(c)
Out[32]: 2
In [33]: next(c)
Out[33]: 3
In [34]: next(c)
Out[34]: 1
来看一下自定义的计数迭代器:
class Count:
def __init__(self, start=0):
self.num = start
def __iter__(self):
return self
def __next__(self):
num = self.num
self.num += 1
return num
Count是一个迭代器同样也是个可迭代对象。__iter__
决定了它是个可迭代对象,每次调用next()
方法会返回num
随即num+=1
生成器
生成器(generator)可以理解为一个比较优雅的迭代器,它的写法会比迭代器简洁的多。它不需要实现__iter__
和__next__
方法,只需要一个yield
关键字即可。
def count(n):
while True:
yield n
n += 1
a = count(0)
print(next(a))
print(next(a))
0
1
或者还有另外一种创建方式,生成器有自己独有的生成器表达式,类似于列表表达式:
In [17]: generator_ex = (x*x for x in range(10))
In [18]: generator_ex
Out[18]: <generator object <genexpr> at 0x10bc232d0>
In [19]: next(generator_ex)
Out[19]: 0
In [23]: sum(generator_ex)
Out[23]: 271
虽然只是一组括号的区别,但是列表表达式返回的是一个列表,而生成器表达式则返回的是generator
对象.
生成器在Python中是一个非常强大的编程结构,可以用更少地中间变量写流式代码,此外,相比其它容器对象它更能节省内存和CPU,当然它可以用更少的代码来实现相似的功能。
总结
- list,dict,str,set,file等都是容易,因为它们都支持for循环所以可以成为可迭代对象
- 如果这些对象都实现了
__iter__
方法,那么它们可以被转换成迭代器对象 - 如果这些对象实现了
__next__
方法则说明它是一个迭代器,迭代器实行的是懒加载方式,它不会把元素都加载一次性加载到内存中,只有到使用的时候才会去加载 - 生成器是一种比较优雅的迭代器,可以通过yield来替代return表现。也可以通过生成表达式来体现。
参考文档https://docs.python.org/3/reference/expressions.html#yieldexpr
参考文档https://docs.python.org/3/library/stdtypes.html#iterator-types