生成器从字面理解就是生成XXX的工具,确实也是如此;在python中是这么定义生成器的:
如下就是一个生成器函数
from collections.abc import Iterable, Iterator, Generator
#生成器Generator
def my_generator_one():
yield 1
yield 2
def my_generator_two():
for i in range(10):
yield i
gen_one = my_generator_one()
gen_two = my_generator_two()
print(gen_one)
print(gen_two)
print("gen_one的数据类型:", type(gen_one))
print("gen_one是否是生成器:", isinstance(gen_one, Generator))
print("gen_two的数据类型:", type(gen_two))
print("gen_two是否是生成器:", isinstance(gen_two, Generator))
>>>
<generator object my_generator_one at 0x0000019F3C76E3C8>
<generator object my_generator_two at 0x0000019F3C76E548>
gen_one的数据类型: <class 'generator'>
gen_one是否是生成器: True
gen_two的数据类型: <class 'generator'>
gen_two是否是生成器: True
从上面结果可以看出,只要函数定义里面有yield关键字,那么这个函数就是生成器函数,调用它们时就会得到一个生成器对象generator object。
从外观上看,生成器函数有下面几个特点(与普通函数的区别)
为了更好的理解生成器,生成器与迭代器的区别和联系,可以先看看我的上篇文章:python中的迭代器,可迭代对象(详细剖析)
为什么说所有的生成器都是迭代器?这是因为生成器默认实现了迭代器协议,即实现了_ _iter _ _ 方法和 _ next _ 方法,下面来验证一下
from collections.abc import Iterable, Iterator, Generator
#生成器Generator
def my_generator_two():
for i in range(10):
yield i
gen_two = my_generator_two()
print("gen_two的数据类型:", type(gen_two))
print("gen_two是否是生成器:", isinstance(gen_two, Generator))
print("gen_two是否是迭代器:", isinstance(gen_two, Iterator))
print(dir(gen_two))
>>>
gen_two的数据类型: <class 'generator'>
gen_two是否是生成器: True
gen_two是否是迭代器: True
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
生成器和迭代器的区别如下
关于迭代器用于从可迭代对象中取出元素可以参考:python中的迭代器,可迭代对象(详细剖析)
,生成器‘‘凭空’生成元素在下面的生成器实现中讲解。
上面说过生成器函数和普通函数的主要区别在于生成器函数有yield关键字,而普通函数没有。如果理解了yield的作用,也就基本知道了生成器的原理。
下面用生成器函数实现并返回斐波那契数列,如下
#生成器Generator
def fibo_generator():
a, b = 0, 1
for i in range(1, 100):
yield a
a, b = b, a+b
gen = fibo_generator()
print("第一次:", gen)
print("第一次:", next(gen))
print("第二次:", gen)
print("第二次:", next(gen))
print("第三次:", gen)
print("第三次:", next(gen))
print("第四次:", gen)
print("第四次:", next(gen))
>>>
第一次: <generator object fibo_generator at 0x000002008ED07F48>
第一次: 0
第二次: <generator object fibo_generator at 0x000002008ED07F48>
第二次: 1
第三次: <generator object fibo_generator at 0x000002008ED07F48>
第三次: 1
第四次: <generator object fibo_generator at 0x000002008ED07F48>
第四次: 2
上面我们定义了一个生成器函数fibo_generator, 变量a, b是斐波那契的第一项和第二项值,然后用yield返回斐波那契的前一百项,不是一次性返回,而是每次返回一个。
从结果可以看到,每调一次fibo_generator函数,就会返回一个生成器,因为生成器又是迭代器。所有我们可以用 next() 来取出生成器生成的值,结果显而易见,feibo_generator每次生成并返回对应的斐波那契数列的数。
以上的这些过程都是 yield 在其中起的作用,具体如下
这两点便是生成器(yield)的主要原理。下面这个案例更能具体体现
def my_generator():
print("start A")
yield 'a'
print("start B")
yield 'b'
print("start C")
yield 'c'
gen = my_generator()
for i in gen:
print(i)
>>>
start A
a
start B
b
start C
c
从结果可以看出,效果如出一辙。不过这里不是直接使用的next()函数从生成器里面取的值,而是利用for关键字来迭代取的,这就是生成器又是迭代器的原因(迭代器可以使用for来迭代取值)。
当我们需要把多个生成器合并一个生成器时,可以这样写
str = 'ABC'
tup = (1, 2, 3)
def my_generator(arg1, arg2):
for i in arg1:
yield i
for j in arg2:
yield j
gen = my_generator(str, tup)
for i in gen:
print(i, end=' ')
>>>
A B C 1 2 3
这样写可以,但是当有多个生成器组成时,上面的写法就会变的比较臃肿,这时python又提供了另外一种写法,即yield from,它的作用完全同每个for循环一样,如下
str = 'ABC'
tup = (1, 2, 3)
def my_generator(arg1, arg2):
yield from arg1
yield from arg2
gen = my_generator(str, tup)
for i in gen:
print(i, end=' ')
通过上面的一通讲解,应该明白了生成器的作用是:要一次数据,就生成一次。,而不是像列表一样,一次性存储所有数据。
由于生成器取一次,生成一次的特点,这使得它在节省内存方面非常优秀,尤其是在处理大数据流方面。
下面简单的比较下它和列表所占内存的大小
import sys
my_list = [x for x in range(10000)]
def my_generator():
for i in range(10000):
yield i
print("列表:", sys.getsizeof(my_list))
print("生成器:", sys.getsizeof(my_generator()))
列表: 87624
生成器: 120
结果显而易见。
以上就是本人对python生成器的一些思考和拙见,如有误,还望指出。如有帮助,请点个赞。