Python yield表达式与send方法深度解析
本文深入解析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学习或工作实践有所帮助。如有疑问,欢迎留言交流。感谢支持。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













