Python yield表达式与send方法深度解析

Noah
Noah
发布时间:2026-10-12| 2人浏览过
原创

本文深入解析Python中yield关键字与send方法的交互机制。通过逐步演示next与send的区别,阐明如何将值传入生成器内部。最后结合装饰器与多阶段管道模型,实现类似Linux grep的递归文件搜索功能,展示yield在复杂数据处理流程中的实际应用。

前言

yield的英文原意为“生产”或“产出”。初次接触Python时,许多开发者对其行为感到困惑。本文旨在梳理yield表达式的核心逻辑,通过实例分析其工作原理,为后续学习提供参考。

基础示例分析

def foo():
 print("starting...")
 while True:
  res = yield
  print("res:",res)

g = foo()
next(g)

当函数定义中包含yield关键字时,调用该函数(如foo())并不会立即执行函数体,而是返回一个生成器对象。此时,可以通过调用next(g)或g.__next__()方法来启动生成器的执行流程。

程序运行输出如下:

starting...

当使用next(g)触发生成器时,代码按顺序从上至下执行。一旦遇到yield语句,执行流立即暂停。

此时,yield表达式后所跟随的值(若有)将被返回给调用者。

打印next(g)的返回值结果如下:

def foo():
 print("starting...")
 while True:
  res = yield
  print("res:",res)

g = foo()
print(next(g))

程序实际输出为:

starting...
None

在上述示例中,首次调用next(g)后,程序暂停在yield所在行。若再次调用next(g),程序将从yield语句之后继续向下执行。

修改代码以多次调用next方法,并打印每次调用的返回值:

def foo():
 print("starting...")
 while True:
  res = yield
  print("res:",res)
g = foo()
print(next(g))
print("*"*20)
print(next(g))

该段代码的执行结果如下:

starting...
None
********************
res: None
None

尽管程序按预期步骤运行,但上述示例存在明显缺陷:变量res的值始终为None,缺乏实际业务意义。

在实际开发中,yield表达式的核心价值在于“接收”一个值,并将其赋值给某个变量,从而赋予生成器处理数据的能力。

那么,如何为res变量赋予具体值呢?关键在于调用生成器的send方法。

send方法不仅能触发生成器继续执行,还能将参数传递给yield表达式,实现数据输入。

基于上述原理修改代码:

def foo():
 print("starting...")
 while True:
  res = yield
  print("res:",res)
g = foo()
next(g)
print(g.send(5))

程序执行结果如下:

starting...
res: 5
None

详细分析上述代码的执行流程:

1. 函数定义完成后,因存在yield关键字,foo()调用仅返回生成器g,并未真正执行函数体。

2. 首次调用next(g)后,函数开始执行。首先执行print语句,随后进入while循环。

3. 当程序执行至yield关键字时,生成器暂停。此时,next(g)调用完成。

4. 接着执行g.send(5)。程序从yield所在行继续向下运行,send方法传递的参数5被赋值给yield表达式。

5. yield接收到send传递的值(即5),并将其赋值给变量res。

6. 由于send方法内部隐含next操作,程序继续向下执行print语句,并再次进入while循环。

7. 程序再次遇到yield。由于此时yield后无后续值,返回None,程序再次暂停,等待下一次next或send调用。

进一步修改代码,多次调用send方法:

def foo():
 print("starting...")
 while True:
  res = yield
  print("res:",res)
g = foo()
next(g)
print(g.send(5))
print("*"*20)
print(g.send(10))
print("#"*20)
print(g.send(15))

执行程序,得到如下输出结果:

starting...
res: 5
None
********************
res: 10
None
####################
res: 15
None

观察可知,代码执行过程与前述分析完全一致。

若调用send方法时传递的参数为None,结果会如何?

根据前文分析,我们可以得出以下结论:

当g.send()向yield传递None时,yield表达式本身没有接收有效值,因此传递给res变量的值为None。由于yield后无其他返回值,程序输出两个None。

通过修改代码验证上述猜想:

def foo():
 print("starting...")
 while True:
  res = yield
  print("res:",res)
g = foo()
next(g)
print("#"*20)
print(g.send(None))

查看程序执行结果:

starting...
####################
res: None
None

结果显示,当send传递None时,程序确实输出了两个None,验证了之前的分析。

利用yield表达式形式实现类似Linux系统中“grep -rl root /etc”命令的功能:

具体代码实现如下:

import os
def init(func):
 def wrapper(*args, **kwargs):
  g = func(*args, **kwargs)
  next(g)
  return g
 return wrapper
@init
def get_file_path(target):
 """
 get file abspath
 # 阶段一:递归找文件的绝对路径,把文件的完事路径发送给阶段二
 :param target:
 :return:
 """
 while True:
  start_path = yield
  g = os.walk(start_path)
  for parent_dir, _, files in g:
   for file in files:
    file_path = r"%s\%s" % (parent_dir, file)
    target.send(file_path)
@init
def opener(target):
 """
 get file obj
 # 阶段二:收到文件的完整路径,打开文件获取文件对象,把文件对象发送给阶段三
 :param target:
 :return:
 """
 while True:
  file_path = yield
  with open(file_path, encoding='utf-8') as f:
   target.send((file_path, f))
@init
def cat_file(target):
 """
 read file content
 # 阶段三:收到文件对象,for循环读取文件的每一行内容,把每一行内容发给阶段四
 :param target:
 :return:
 """
 while True:
  file_path, f = yield
  for line in f:
   file_content = target.send((file_path, line))
   if file_content:
    break
@init
def grep(target, pattern):
 """
 grep function
 # 阶段四:收到文件的一行内容,判断要查找的内容是否在这一行中,如果在,则把文件名发送给阶段五
 :param target:
 :param pattern:
 :return:
 """
 tag = False
 while True:
  file_path, line = yield tag
  tag = False
  if pattern in line:
   target.send(file_path)
   tag = True
@init
def printer():
 """
 print file name
 # 阶段五:收到文件名,打印结果
 :return:
 """
 while True:
  filename = yield
  print(filename)
path1 = "/root"   # 定义要搜索的路径
path2 = "/etc"   # 定义要搜索的路径
g = get_file_path(opener(cat_file(grep(printer(), "root"))))
print(g)
g.send(path1)
g.send(path2)

总结

本文详细阐述了Python中yield表达式与send方法的配合机制,并通过多阶段管道模型展示了其在文件搜索等复杂场景中的应用。希望本文内容对读者的Python学习或工作实践有所帮助。如有疑问,欢迎留言交流。感谢支持。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。