返回生成器表达式与使用yield的差异:文件IO报错原因解析
问题:两段Python代码执行结果不一致的原因解析
两段Python代码,一段能正常执行,另一段触发ValueError: I/O operation on closed file.,下面解析两者成败的原因,并明确返回生成器表达式与使用yield的核心差异。
无效代码
def get_file_rows(path: str): with open(path, "r") as file: lines = (line.strip() for line in file) return lines lines = get_file_rows("test.txt") for line in lines: print(line)
有效代码
def get_file_rows(path: str): with open(path, "r") as file: for line in file: yield line.strip() lines = get_file_rows("test.txt") for line in lines: print(line)
原因解析
无效代码报错原因
with语句的核心作用是代码块结束后自动关闭资源(这里是文件)。这段代码里,生成器表达式(line.strip() for line in file)虽然定义在with块内,但生成器是惰性求值的——定义时不会立刻读取文件内容,只有当后续执行for line in lines进行迭代时,才会真正去读取文件。
当get_file_rows函数执行到return lines时,with代码块已经执行完毕,文件已经被自动关闭。此时再去迭代生成器,就会尝试对已关闭的文件进行I/O操作,直接触发ValueError。
有效代码执行成功原因
使用yield的函数是生成器函数,调用它不会立刻执行函数体,只会返回一个生成器对象。只有当迭代这个生成器(比如for line in lines)时,函数体才会逐步执行:
- 第一次迭代时,函数进入
with块打开文件,读取一行并yield处理后的内容,此时函数暂停执行但不会退出with块,文件保持打开状态; - 后续每次迭代,函数从暂停的位置继续执行,读取下一行并
yield; - 直到文件读取完毕,函数执行到
with块结束,文件才会被自动关闭。
整个迭代过程都在with块的上下文内完成,文件始终处于合法的打开状态,因此不会报错。
返回生成器表达式与使用yield的核心差异
- 上下文生命周期与执行时机
- 返回生成器表达式:生成器的定义在
with块内,但迭代发生在with块外,此时文件已关闭,上下文已经失效,无法再进行合法的文件操作。 - 使用
yield的生成器函数:迭代过程完全在with块的上下文内进行,文件在整个迭代周期中保持打开状态,直到迭代结束才会被关闭。
- 返回生成器表达式:生成器的定义在
- 函数执行流程
- 返回生成器表达式的函数:调用时会完整执行整个函数体(包括进入和退出
with块),返回生成器后函数就已经执行完毕。 - 生成器函数:调用时仅返回生成器对象,函数体在每次迭代时才逐步执行,
yield会暂停函数并保留当前上下文,直到下一次迭代触发或迭代完全结束。
- 返回生成器表达式的函数:调用时会完整执行整个函数体(包括进入和退出
内容的提问来源于stack exchange,提问作者Daniel Aviv
相关产品推荐
相关产品推荐

