# 3.6 【基础】生成器
## 1. 什么是生成器?
生成器(英文名 Generator ),是一个可以像迭代器那样使用for循环来获取元素的函数。
生成器的出现(Python 2.2 +),实现了延时计算,从而缓解了在大量数据下内存消耗过猛的问题。
当你在 Python Shell 中敲入一个生成器对象,会直接输出 `generator object` 提示你这是一个生成器对象
```python
>>> gen = (i for i in range(5))
>>> gen
<generator object <genexpr> at 0x10cae50b0>
```
## 2. 如何创建生成器?
### 使用列表推导式
在上面已经演示过,正常我们使用列表推导式时是下面这样子,使用 `[]` ,此时生成的是列表。
```python
>>> mylist = [i for i in range(5)]
>>> mylist
[0, 1, 2, 3, 4]
```
而当你把 `[]` 换成 `()` ,返回的就不是列表了,而是一个生成器
```python
>>> gen = (i for i in range(5))
>>> gen
<generator object <genexpr> at 0x10cae50b0>
```
### 使用 yield
`yield` 是什么东西呢? 它相当于我们函数里的 return,但与 return 又有所不同。
- 当一个函数运行到 yield 后,函数的运行会暂停,并且会把 yield 后的值返回出去。
- 若 yield 没有接任何值,则返回 None
- yield 虽然返回了,但是函数并没有结束
请看如下代码,我定义了一个 `generator_factory` 函数,当我执行 `gen = generator_factory()` 时,gen 就是一个生成器对象
```python
>>> def generator_factory(top=5):
... index = 0
... while index < top:
... print("index 值为: " + str(index))
... index = index + 1
... yield index
... raise StopIteration
...
>>> gen = generator_factory()
>>> gen
<generator object generator_factory at 0x1018340b0>
```
## 3. 生成器的使用
从一个生成器对象中取出元素,和我们前面学过的通过切片访问列表中的元素不一样,它没有那么直观。
想要从生成器对象中取出元素,只有两种方法:
**第一种方法**:使用 next 方法一个一个地把元素取出来,如果元素全部取完了,生成器会抛出 `StopIteration` 的异常。
```python
>>> gen = (x for x in range(3))
>>> gen
<generator object <genexpr> at 0x1072400b0>
>>> next(gen)
0
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
```
**第二种方法**:使用 for 循环一个一个地迭代出来
```python
>>> gen = (x for x in range(3))
>>> for i in gen:
... print(i)
...
0
1
2
```
## 4. 生成器的激活
生成器对象,在创建后,并不会执行任何的代码逻辑。
想要从生成器对象中获取元素,那么第一步要触发其运行,在这里称之为激活。
方法有两种:
1. 使用`next()` :上面已经讲过
2. 使用`generator.send(None)`
还以下面这段代码为例,可以看到 `gen.send(None)` 相当于执行了 `next(gen)`
```python
>>> def generator_factory(top=5):
... index = 0
... while index < top:
... print("index 值为: " + str(index))
... index = index + 1
... yield index
... raise StopIteration
...
>>>
>>> gen = generator_factory()
>>> gen.send(None)
index 值为: 0
1
>>> gen.send(None)
index 值为: 1
2
```
## 5. 生成器的状态
生成器在其生命周期中,会有如下四个状态
- `GEN_CREATED` # 生成器已创建,还未被激活
- `GEN_RUNNING` # 解释器正在执行(只有在多线程应用中才能看到这个状态)
- `GEN_SUSPENDED` # 在 yield 表达式处暂停
- `GEN_CLOSED` # 生成器执行结束
通过下面的示例可以很轻松地理解这一过程(`GEN_RUNNING` 这个状态只有在多线程中才能观察到,这里就不演示啦)
```python
>>> gen = (x for x in range(2))
>>> from inspect import getgeneratorstate
>>> gen = (x for x in range(3))
>>> getgeneratorstate(gen)
'GEN_CREATED'
>>>
>>> next(gen)
0
>>> getgeneratorstate(gen)
'GEN_SUSPENDED'
>>> next(gen)
1
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>> getgeneratorstate(gen)
'GEN_CLOSED'
```
## 6. 生成器的异常
在最前面,我有定义了一个生成器函数。
```python
def generator_factory(top=2):
index = 0
while index < top:
index = index + 1
yield index
raise StopIteration
```
在没有元素可返回时,我最后抛出了 `StopIteration` 异常,这是为了满足生成器的协议。
实际上,如果你不手动抛出 `StopIteration`,在生成器遇到函数 return 时,会我自动抛出 `StopIteration`。
请看下面代码,我将 `raise StopIteration` 去掉后,仍然会抛出异常。
```python
>>> def generator_factory(top=2):
... index = 0
... while index < top:
... index = index + 1
... yield index
...
>>> gen = generator_factory()
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
```
- 第一章:安装运行
- 1.1 【环境】快速安装 Python 解释器
- 1.2 【环境】Python 开发环境的搭建
- 1.3 【基础】两种运行 Python 程序方法
- 第二章:数据类型
- 2.1 【基础】常量与变量
- 2.2 【基础】字符串类型
- 2.3 【基础】整数与浮点数
- 2.4 【基础】布尔值:真与假
- 2.5 【基础】学会输入与输出
- 2.6 【基础】字符串格式化
- 2.6 【基础】运算符(超全整理)
- 第三章:数据结构
- 3.1 【基础】列表
- 3.2 【基础】元组
- 3.3 【基础】字典
- 3.4 【基础】集合
- 3.5 【基础】迭代器
- 3.6 【基础】生成器
- 第四章:控制流程
- 4.1 【基础】条件语句:if
- 4.2 【基础】循环语句:for
- 4.3 【基础】循环语句:while
- 4.4 【进阶】五种推导式
- 第五章:学习函数
- 5.1 【基础】普通函数
- 5.2 【基础】匿名函数
- 5.3 【基础】高阶函数
- 5.4 【基础】反射函数
- 5.5 【基础】偏函数
- 5.6 【进阶】泛型函数
- 5.7 【基础】变量的作用域
- 5.8 【进阶】上下文管理器
- 5.9 【进阶】装饰器的六种写法
- 第六章:错误异常
- 6.1 【基础】什么是异常?
- 6.2 【基础】如何抛出和捕获异常?
- 6.3 【基础】如何自定义异常?
- 6.4 【进阶】如何关闭异常自动关联上下文?
- 6.5 【进阶】异常处理的三个好习惯
- 第七章:类与对象
- 7.1 【基础】类的理解与使用
- 7.2 【基础】静态方法与类方法
- 7.3 【基础】私有变量与私有方法
- 7.4 【基础】类的封装(Encapsulation)
- 7.5 【基础】类的继承(Inheritance)
- 7.6 【基础】类的多态(Polymorphism)
- 7.7 【基础】类的 property 属性
- 7.8 【进阶】类的 Mixin 设计模式
- 7.9 【进阶】类的魔术方法(超全整理)
- 7.10 【进阶】神奇的元类编程(metaclass)
- 7.11 【进阶】深藏不露的描述符(Descriptor)
- 第八章:包与模块
- 8.1 【基础】什么是包、模块和库?
- 8.2 【基础】安装第三方包的八种方法
- 8.3 【基础】导入单元的构成
- 8.4 【基础】导入包的标准写法
- 8.5 【进阶】常规包与空间命名包
- 8.6 【进阶】花式导包的八种方法
- 8.7 【进阶】包导入的三个冷门知识点
- 8.8 【基础】pip 的超全使用指南
- 8.9 【进阶】理解模块的缓存
- 8.10 【进阶】理解查找器与加载器
- 8.11 【进阶】实现远程导入模块
- 8.12 【基础】分发工具:distutils和setuptools
- 8.13 【基础】源码包与二进制包有什么区别?
- 8.14 【基础】eggs与wheels 有什么区别?
- 8.15 【进阶】超详细讲解 setup.py 的编写
- 8.16 【进阶】打包辅助神器 PBR 是什么?
- 8.17 【进阶】开源自己的包到 PYPI 上
- 第九章:调试技巧
- 9.1 【调试技巧】超详细图文教你调试代码
- 9.2 【调试技巧】PyCharm 中指定参数调试程序
- 9.3 【调试技巧】PyCharm跑完后立即进入调试模式
- 9.4 【调试技巧】脚本报错后立即进入调试模式
- 9.5 【调试技巧】使用 PDB 进行无界面调试
- 9.6 【调试技巧】如何调试已经运行的程序?
- 9.7 【调试技巧】使用 PySnopper 调试疑难杂症
- 9.8 【调试技巧】使用 PyCharm 进行远程调试
- 第十章:并发编程
- 10.1 【并发编程】从性能角度初探并发编程
- 10.2 【并发编程】创建多线程的几种方法
- 10.3 【并发编程】谈谈线程中的“锁机制”
- 10.4 【并发编程】线程消息通信机制
- 10.5 【并发编程】线程中的信息隔离
- 10.6 【并发编程】线程池创建的几种方法
- 10.7 【并发编程】从 yield 开始入门协程
- 10.8 【并发编程】深入理解yield from语法
- 10.9 【并发编程】初识异步IO框架:asyncio 上篇
- 10.10 【并发编程】深入异步IO框架:asyncio 中篇
- 10.11 【并发编程】实战异步IO框架:asyncio 下篇
- 10.12 【并发编程】生成器与协程,你分清了吗?
- 10.14 【并发编程】浅谈线程安全那些事儿
- 第十二章:虚拟环境
- 12.1 【虚拟环境】为什么要有虚拟环境?
- 12.2 【虚拟环境】方案一:使用 virtualenv
- 12.3 【虚拟环境】方案二:使用 pipenv
- 12.4 【虚拟环境】方案三:使用 pipx
- 12.5 【虚拟环境】方案四:使用 poetry
- 第十三章:绝佳工具
- 13.1 【静态检查】mypy 的使用
- 13.2 【代码测试】pytest 的使用
- 13.3 【代码提交】pre-commit hook
- 13.4 【项目生成】cookiecutter 的使用
- 第十四章:数据可视化
- 14.1 【可视化之matplotlib】一图带你入门matplotlib
- 14.2 【可视化之matplotlib】详解六种可视化图表
- 14.3 【可视化之matplotlib】 绘制正余弦函数图象
- 14.4 【可视化之matplotlib】难点:子图与子区
- 14.5 【可视化之matplotlib】绘制酷炫的gif动态图
- 14.6 【可视化之matplotlib】自动生成图像视频
- 14.7 【可视化神器】最高级的可视化神器: plotly_express