生成器

浏览 973

课文

什么是生成器

在写迭代器的时候我们每次都要定义一个 class ,实现其中的 __iter____next__ 是不是略感麻烦。

之前返回一个小于等于指定值的正整数的迭代器我们就可以用生成器来这么写。

def positive(limit):
    n = 1
    while n <= limit:
        yield n
        n += 1

for n in positive(5):
    print(n)
1
2
3
4
5

是不是感觉代码量一下子缩短了一大半。

使用了 yield 的函数便是生成器。

我们来看下生成器所实现了的方法有哪些。

...
generator = positive(5)

print(dir(generator))
['__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']

一个 生成器 对象自然而然的就包含了 __iter____next__ ,这意味着 生成器 必然就是一个 可迭代对象迭代器 。这是一个从属关系,一个 可迭代对象迭代器 不一定是 生成器

yield 关键字

我们都知道,获取 迭代器 的元素时要通过 next 方法,而 yield 关键字的作用就在于在每次执行 next 方法时执行到 yield 的位置返回值同时退回到 next 调用的位置继续执行。

看文字能学会代码的话我们还要键盘干嘛,来看一段代码。这个过程在交互模式下比较容易理解,需要动动手去实践一下。

def my_gen():
    print('执行一次')
    yield '第一次 yield'

    print('执行两次')
    yield '第二次 yield'

    print('执行三次')
    yield '第三次 yield'

    print('执行四次')

gen = my_gen()

image

在第一次执行 next 函数时,会从 my_gen 函数开头执行到第一个 yield 关键字,期间打印了 执行一次 ,返回了字符串 第一次 yield 到变量 result ,并回退到 next 执行的地方继续往下执行。

循环往复直到函数执行结束抛出 StopIteration 异常。

yield from 关键字

yield from 后面接一个 可迭代对象 ,等价于用 for in 去单独 yield

list1 = [1, 2, 3, 4]
list2 = ['a', 'b', 'c', 'd']

def my_gen_1():
    yield from list1
    yield from list2

def my_gen_2():
    for n in list1:
        yield n

    for n in list2:
        yield n

print('my_gen_1 循环结果:', [x for x in my_gen_1()])
print('my_gen_2 循环结果:', [x for x in my_gen_2()])
my_gen_1 循环结果: [1, 2, 3, 4, 'a', 'b', 'c', 'd']
my_gen_2 循环结果: [1, 2, 3, 4, 'a', 'b', 'c', 'd']

send 方法实现数据的双向通信

我们刚刚都还只是通过 yield 向生成器外部传值,实际上我们也可以向生成器内部传值。

def double_money():
    print('请输入你的金钱,我来负责乘2,(机会只有三次)')

    # 需要先执行一次 send(None) 到这里
    x = yield

    x = yield x * 2

    x = yield x * 2

    x = yield x * 2

double = double_money()

# 必须先执行一次 send None
# 执行到第一个 yield 关键字
double.send(None)

print(double.send(10))
print(double.send(20))
print(double.send(30))
请输入你的金钱,我来负责乘2,(机会只有三次)
20
40
60

我们还可以实现一个求平均数的生成器。

def average_gen():
    count = 0
    n = yield
    while True:
        count += 1
        n += yield n / count

av = average_gen()

av.send(None)
print(av.send(10))
print(av.send(20))
print(av.send(30))
10.0
15.0
20.0

使用生成器的场景

由于可以使用生成器很方便地实现一个迭代器,因此迭代器适用的场景生成器几乎都适用。

  • 节省内存
  • 流式处理数据
  • 无限的数据

由于生成器可以通过 yieldsend 中断执行并双向通信,因此生成器在 python 中被用来作为 协程 的一种实现,这部分知识到协程的章节将会着重介绍。

评论

登录参与讨论

暂无评论

共 0 条
  • 1
前往
  • 1

课程目录

第一课:命名空间与作用域 第二课:闭包 第三课:装饰器 第四课:迭代器 第五课:生成器

学习遇到困难?微信扫码进入社群与小伙伴一起交流讨论。