生成器

浏览 91

课文

## 什么是生成器 在写迭代器的时候我们每次都要定义一个 `class` ,实现其中的 `__iter__` 与 `__next__` 是不是略感麻烦。 之前返回一个小于等于指定值的正整数的迭代器我们就可以用生成器来这么写。 ```python def positive(limit): n = 1 while n <= limit: yield n n += 1 for n in positive(5): print(n) ``` ```python 1 2 3 4 5 ``` 是不是感觉代码量一下子缩短了一大半。 使用了 `yield` 的函数便是生成器。 我们来看下生成器所实现了的方法有哪些。 ```python ... generator = positive(5) print(dir(generator)) ``` ```output ['__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` 调用的位置继续执行。 看文字能学会代码的话我们还要键盘干嘛,来看一段代码。这个过程在交互模式下比较容易理解,需要动动手去实践一下。 ```output def my_gen(): print('执行一次') yield '第一次 yield' print('执行两次') yield '第二次 yield' print('执行三次') yield '第三次 yield' print('执行四次') gen = my_gen() ``` ![image](https://qiniu.3yya.com/2ca87e0bdb11d38631b5859c5b2a7d6c/2ca87e0bdb11d38631b5859c5b2a7d6c.png) 在第一次执行 `next` 函数时,会从 `my_gen` 函数开头执行到第一个 `yield` 关键字,期间打印了 `执行一次` ,返回了字符串 `第一次 yield` 到变量 `result` ,并回退到 `next` 执行的地方继续往下执行。 循环往复直到函数执行结束抛出 `StopIteration` 异常。 ### yield from 关键字 `yield from` 后面接一个 `可迭代对象` ,等价于用 `for in` 去单独 `yield`。 ```python 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()]) ``` ```output my_gen_1 循环结果: [1, 2, 3, 4, 'a', 'b', 'c', 'd'] my_gen_2 循环结果: [1, 2, 3, 4, 'a', 'b', 'c', 'd'] ``` ### send 方法实现数据的双向通信 我们刚刚都还只是通过 `yield` 向生成器外部传值,实际上我们也可以向生成器内部传值。 ```python 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)) ``` ```output 请输入你的金钱,我来负责乘2,(机会只有三次) 20 40 60 ``` 我们还可以实现一个求平均数的生成器。 ```python 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)) ``` ```output 10.0 15.0 20.0 ``` ## 使用生成器的场景 由于可以使用生成器很方便地实现一个迭代器,因此迭代器适用的场景生成器几乎都适用。 - 节省内存 - 流式处理数据 - 无限的数据 由于生成器可以通过 `yield` 与 `send` 中断执行并双向通信,因此生成器在 python 中被用来作为 `协程` 的一种实现,这部分知识到协程的章节将会着重介绍。

评论

登录参与讨论

暂无评论

共 0 条
  • 1
前往
  • 1

课程目录

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

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