你还在为数据生成逻辑和数据的消费逻辑难以分离而苦恼嘛?你还在为生成大量数据而担心内存不足嘛?来用python吧 —— 松言松语
生成器
从迭代器(Iterator
)中循环遍历取出元素来处理,叫做迭代。在python
中,定义了__next__
方法的类,就是一个迭代器。
生成器,是数据的生产者。先说下使用到生成器的典型场景,比如:
- 复制文件的时候 -> 边读边写
- 获取1-10000里面所有的质数 -> 多次判断是否是质数
生成器的场景特征
上述场景的特征是:
这些场景的特点是:
- 重复计算。比如循环、轮询;
- 占用较多的空间、空间。生成大量的计算值结果集一并返回;
- 需要用到计算出来的值。用这些值做一些具体业务处理;
生成器的处理流程
通过一个例子来看一下怎么使用:
读取文件并输出文件内容
def generator(filepath):
block_size = 1024
with open(filepath, 'r', encoding="utf-8") as f:
while True:
block = f.read(block_size)
if block:
# 1. 注意这里有一个yield关键字,表示返回这个变量
yield block
else:
break
# 2. 调用generator,返回的是一个迭代器
it = generator("C:\\Users\\广西北部湾银行\\Desktop\\会计分录.txt")
# 3. 遍历迭代器,拿到generator中 yield(简单理解,就是’生成‘)的变量
for block in it:
print(block)
我们先定义了一个generator
函数,用来读取文件。在generator
函数中yield
(返回)一个叫做block
的变量。
一旦函数中存在yield
关键字,调用该函数的时候,便不会返回函数内的return值
,而是返回一个可以迭代器it
。
梳理一下执行的流程:
- 调用生成器
generator
,返回一个迭代器,for
自动调用内置函数next()
进行迭代操作; - 函数执行到关键字
yield
的时候会先暂停,先将yield
的变量返回给调用next()
的一方; - 调用方处理这个值后,控制权再回到生成器中
yield
关键字的位置继续往下执行。如此重复。
需要知道的其他细节:
如果不用
for
循环的话,可以手动调用内置函数next()
来获取迭代器的下一个值;yield
关键字在生成器中的作用是:返回一个值给调用next()的一方,所以一般写法就是yield变量
。当数据都yield完了以后(如循环结束,不再有yield可以暂停),会报一个
StopIteration
的异常,需要手动处理。若调用方使用for
来遍历,它已经自动帮我们捕获这个异常并停止调用next()
,故无须额外处理。
生成器的优势
快,且减少内存压力,不用一次性生成所有的计算值一起返回;
值的处理
逻辑和值的生成
逻辑分离;
yield的常见写法
| 写法 | 描述 | |
| ——————— | ———— | ———————————————————— |
| result = yield 变量
| 常见于协程 | 1. 先变量返回给生产者(常见于需要返回协程结果的场景);
2. 暂停代码,等待外部传入一个值;
3. 外部传入值后,将其赋值给result
; |
| result = yield
| 常见于协程 | 和上一种写法一样,相当于yield回去一个None;
|
| yield 变量
| 常见于生成器 | 只负责返回(生成)一个值 |
多个yield & yield from
在一个生成器中可以存在多个yield
关键字。
逻辑依然不变:代码只要碰到yield
就返回,重新拿到控制权后代码继续往下走。
def create():
yield "from create"
def generator():
yield 1
yield 2
yield 3
print("----------")
yield from [11, 22]
print("----------")
yield from range(3)
print("----------")
yield from create()
for i in generator():
print(i)
输出结果为:
1
2
----------
11
22
----------
0
1
----------
from create
这里面还需要注意yield from
关键字,它表示从一个集合、列表、或者从另外一个生成器中返回值。