python中的生成器

一 什么是生成器?

生成器从字面理解就是生成XXX的工具,确实也是如此;在python中是这么定义生成器的:

  • 只要在python的函数定义中有yield关键字,那么该函数就是一个生成器函数,调用该函数时就会返回一个生成器对象。

如下就是一个生成器函数

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。
从外观上看,生成器函数有下面几个特点(与普通函数的区别)

  • 生成器函数的定义体里面有yield关键字,而普通函数没有
  • 生成器函数里面没有return语句,yield就相当于return,普通函数有return语句

二 生成器,迭代器的区别与联系

为了更好的理解生成器,生成器与迭代器的区别和联系,可以先看看我的上篇文章:python中的迭代器,可迭代对象(详细剖析)

1.生成器和迭代器的联系
  • 所有的生成器都是迭代器,生成器是迭代器的一种

为什么说所有的生成器都是迭代器?这是因为生成器默认实现了迭代器协议,即实现了_ _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']

2.生成器和迭代器的区别

生成器和迭代器的区别如下

  • 一般而言,迭代器用于从可迭代对象中取出元素,而生成器则是‘凭空’生成元素

关于迭代器用于从可迭代对象中取出元素可以参考:python中的迭代器,可迭代对象(详细剖析)
,生成器‘‘凭空’生成元素在下面的生成器实现中讲解。

三 生成器的实现原理

1.生成器的实现原理

上面说过生成器函数和普通函数的主要区别在于生成器函数有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关键字时 ,生成器函数会暂停执行该函数的后续代码,记住此时的位置,并且返回一个生成器对象generator object(相当于return),生成器中存储了生成的值。
  • 下次再调用该生成器函数时,程序将从上一次暂停的地方继续往下执行,如果生成器无法继续生成下一个值,并会抛出StopIteration异常,生成器函数结束执行。

这两点便是生成器(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来迭代取值)。

2.yield from

当我们需要把多个生成器合并一个生成器时,可以这样写

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=' ')

3.生成器的应用

通过上面的一通讲解,应该明白了生成器的作用是:要一次数据,就生成一次。,而不是像列表一样,一次性存储所有数据。
由于生成器取一次,生成一次的特点,这使得它在节省内存方面非常优秀,尤其是在处理大数据流方面。
下面简单的比较下它和列表所占内存的大小

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生成器的一些思考和拙见,如有误,还望指出。如有帮助,请点个赞。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值