yield
:生成器在 Python 中,yield
是一个极其强大但有时被初学者忽视的关键字。它不仅改变了我们对函数行为的理解,还为我们提供了一种高效处理大数据、流式数据和惰性求值的重要手段。本文将深入解析 yield
的使用方法,并通过多个示例展示其强大的功能。
yield
简单来说,yield
是用于定义 生成器函数(generator function) 的关键字。不同于普通的 return
函数会立即返回所有结果,使用 yield
的函数会在每次迭代时逐步产生结果。
return
的普通函数:def get_numbers_return(n):
return list(range(n))
print(get_numbers_return(5)) # 输出: [0, 1, 2, 3, 4]
yield
的生成器函数:def get_numbers_yield(n):
for i in range(n):
yield i
gen = get_numbers_yield(5)
print(gen)
print("=====")
for num in gen:
print(num)
# 输出:
<generator object get_numbers_yield at 0x100a4ea80>
=====
# 0
# 1
# 2
# 3
# 4
可以看到,get_numbers_yield
并不会一次性返回整个列表,而是每次调用 __next__()
时按需生成一个值。
特性 | 迭代器(Iterator) | 生成器(Generator)特殊的迭代器 |
---|---|---|
创建方式 | 实现 __iter__ 和 __next__ 方法 |
使用 yield 的函数 |
内存占用 | 手动维护状态 | 自动维护内部状态 |
简洁性 | 相对复杂 | 更简洁,易于编写 |
延迟加载 | 可以实现 | 天生支持延迟加载 |
✅ 总结:如果你需要实现一个逐个返回值的函数,优先考虑使用
yield
来创建生成器。
yield
的工作原理当 Python 解释器遇到含有 yield
的函数时,会自动将其转换为一个 生成器对象,而不是直接执行函数体。
函数定义 -> 调用函数 -> 返回生成器对象
-> 启动迭代 -> 执行直到第一个 yield
-> yield 返回值 -> 暂停并保存状态
-> 下次 next() -> 从暂停处继续执行
-> 继续直到下一个 yield 或函数结束
def simple_generator():
print("开始执行")
yield 1
print("第一次 yield 结束")
yield 2
print("第二次 yield 结束")
g = simple_generator()
print(next(g)) # 输出:开始执行 → 1
print(next(g)) # 输出:第一次 yield 结束 → 2
print(next(g)) # 抛出 StopIteration 异常
yield
的高级用法send()
方法除了 next()
,你还可以使用 send(value)
在生成器运行时向其发送数据。
def echo():
while True:
message = yield
print(f"收到消息: {message}")
e = echo()
next(e) # 必须先触发一次,进入 yield 状态
e.send("你好") # 收到消息: 你好
e.send("再见") # 收到消息: 再见
⚠️ 注意:首次必须调用
next()
或send(None)
来启动生成器。
yield
也是早期 Python 协程的基础,在 async/await
出现之前,很多库如 Tornado
就是基于生成器实现异步逻辑。
def coroutine_example():
x = yield "等待输入"
print("收到:", x)
c = coroutine_example()
print(next(c)) # 输出: 等待输入
c.send("Hello") # 输出: 收到: Hello
类似于列表推导式,Python 也支持生成器表达式,但它是惰性的。
ge = (x * x for x in range(5))
for item in ge:
print(item)
这比 list(x*x for x in range(5))
更节省内存,尤其适合处理大数据集。
yield from
:子生成器委托Python 3.3 引入了 yield from
,允许一个生成器将部分操作委托给另一个生成器或可迭代对象。
def sub_generator():
yield "A"
yield "B"
def main_generator():
yield from sub_generator()
yield "C"
for item in main_generator():
print(item)
# 输出:
# A
# B
# C
这在构建复杂的生成器链时非常有用。
yield
的典型应用场景def read_large_file(file_path):
with open(file_path, 'r') as f:
for line in f:
yield line.strip()
for line in read_large_file('huge_log.txt'):
process_line(line)
避免一次性读取整个文件,提高性能和稳定性。
def numbers():
for i in range(10):
yield i
def filter_even(nums):
for n in nums:
if n % 2 == 0:
yield n
def square(nums):
for n in nums:
yield n * n
pipeline = square(filter_even(numbers()))
for result in pipeline:
print(result)
这种“流水线”风格非常适合做 ETL 流水线、日志处理等工作。
正如你在之前的 WSGI 示例中看到的:
class Response:
def __call__(self, environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
yield self.body # 字节流响应体
这是符合 WSGI 接口规范的一种推荐做法,可以让服务器逐步发送响应内容,不阻塞主线程。
优点 | 说明 |
---|---|
惰性求值 | 不必预先计算所有结果,节省内存 |
高效处理大数据 | 适用于无穷序列、大文件、流式数据等场景 |
简化状态管理 | 自动保存函数内部状态,无需手动控制指针 |
适用于异步/协程编程 | 在早期异步框架中扮演关键角色 |
yield
。yield
和函数组合能写出优雅又高效的代码。yield
是 Python 中最具魅力的语言特性之一。它不仅仅是语法糖,更是通往高效、优雅编程之路的重要工具。掌握 yield
,你就能够写出更清晰、更模块化、更可控的数据处理逻辑。
PEP 255 – Simple Generators