Python中的生成器是什么

生成器的工作原理

只要Python函数的主体中有yield关键字,该函数就是生成器函数。调用生成器函数,返回一个生成器对象。也就是说,生成器函数是生成器工厂。

下面以一个简单的函数说明生成器的行为:

def gen123():
    yield 1
    yield 2
    yield 3


print(gen123)  # <function gen123 at 0x000002A486B4A200>
print(gen123())  # <generator object gen123 at 0x000002A486AF7270>
for i in gen123():
    print(i)  # 1,2,3

g = gen123()
print(next(g))  # 1
print(next(g))  # 2
print(next(g))  # 3
print(next(g))  # StopIteration

可以看出,在函数主体中我们使用了3个yield,输出gen123是函数对象,但是gen123()是个生成器对象。生成器对象实现了Iterator接口,因此生成器对象可以迭代。我们把gen123()赋值给g,因为g是迭代器,所以调用next(g)会获取yield产出下一项,直到所有项产出完以后,抛出StopIteration异常。

生成器函数创建一个生成器对象,包装生成器函数的主体。把生成器对象传递给next()时,生成器函数提前执行函数主体中的下一个yield语句, 返回产出的值,并在函数主体的当前位置暂停。最终,函数的主体返回时,Python创建的外层生成器对象抛出StopIteration异常。

惰性生成器

我们看下面的代码:

import re
import reprlib

RE_WORD = re.compile(r'\w+')


class Sentence:
    def __init__(self, text):
        self.text = text
        self.words = RE_WORD.findall(text)

    def __repr__(self):
        return f"Sentence({reprlib.repr(self.text)})"

    def __iter__(self):
        for word in self.words:
            yield word

上面代码Sentence.__iter__是一个生成器函数,虽然如此,但是还不够惰性,因为__init__已经及早的构建了数据列表且绑定到self.words属性上,这样已经处理了整个文本,列表使用的内存和文本本身一样多,如果我们只想迭代前面几个单词,那么大多数工作都是在白费力气。

如今,人们认为惰性是好的特质,至少在编程语言和API中是如此。惰性实现是指尽可能延后生成值。这样能节省内存,或许还可以避免浪费CPU循环。那么上面代码可以实现惰性操作吗?那必须滴。

finditer()是findall()的惰性版本,返回的不是一个列表,而是一个生成器,按需产出re.MatchObject实例,如果文本够长,finditer()能节省大量内存,因为它只在需要时从文本读取单词。

惰性生成器表达式

生成器已经极大简化代码了,不过使用生成器表达式能让代码更精简。

def __iter__(self):
    return (match.group() for match in RE_WORD.finditer(self.text))
s = Sentence('你好 谢谢 再见')
for i in s:
    print(i)
# 输出
# 你好
# 谢谢
# 再见

和上面代码唯一区别就是,__iter__方法这里使用的不是生成器函数(没有yield),而是使用生成器表达式构建生成器。然后将其返回。不过,最终效果都是一样的:__iter__方法的调用得到一个生成器对象。

生成器表达式是语法糖(( i for i in xxx)),完全可以用生成器函数替代,不过有时候使用生成器表达式更便利。

何时使用生成器表达式

生成器表达式是创建生成器的简洁句法,无须先定义后调用。不过生成器函数更为灵活,可以使用多个语句实现复杂逻辑,甚至可以作为协程使用。

根据我的经验:如果生成器表达式要分成多行编写,那我更倾向于定义生成器函数,增强代码的可读性。

对比迭代器和生成器

迭代器:

泛指实现了__next__方法的对象。迭代器用于生成供客户代码使用的数据,即客户代码通过for循环或其他迭代方式,或者直接在迭代器上调用next(it)驱动迭代器。不过显示调用next()不常见。实际上,Python中使用的迭代器多数是生成器。

生成器:

由Python编译器构造的迭代器。为了构建生成器,我们不使用__next__方法,而是使用yield关键字得到生成器函数。。生成器表达式是构建生成器对象的 另一种方式。生成器对象提供了 __next__ ⽅法,因此⽣成器对 象是迭代器。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值