如果在“with”块内完成,“yield”会触发 __exit__ 函数吗?
Posted
技术标签:
【中文标题】如果在“with”块内完成,“yield”会触发 __exit__ 函数吗?【英文标题】:Does "yield"-ing trigger the __exit__ function if done inside a "with" block? 【发布时间】:2017-07-04 15:40:29 【问题描述】:This question as been asked with respect to returning 来自 with
块内部,但是让步怎么样?
如果下次调用该函数,是否会在 yield
上调用块的 __exit__
,然后再次调用 __enter__
?还是等待生成器退出 with 块(或返回)
举个例子:
def tmp_csv_file():
tmp_path = 'tmp.csv'
with open(tmp_path, 'w+') as csv_file:
yield csv_file # will this close the file?
os.remove(tmp_path)
【问题讨论】:
我认为不会 - 生成器保存它们产生的状态,以便它们可以从那个点继续。您没有使用内置的tempfile 有什么原因吗? 你不能自己测试一下吗(因为你显然也没有花时间阅读文档)? 注意:“with”块被称为上下文管理器。 :) @TemporalWolf 我不知道它的存在,谢谢你的提示! @martineau 绝对可以并且将会有,但是我在 SO 上发帖也是为了让其他人可以访问答案。此外,MSeifert 的回答发现了一些我不会测试的点,所以在这里询问确实得到了回报:) 【参考方案1】:这取决于:
如果您的生成器函数超出范围(或以其他方式被删除),则调用 __exit__
。
如果生成器耗尽,则调用__exit__
。
=> 只要生成器在 with-block 中并且没有耗尽,并且保存生成器的变量没有被删除,__exit__
就不会被调用。
例如:
class Test(object):
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self, *args, **kwargs):
print('exit')
def funfunc():
with Test():
yield 1
yield 2
测试它:
>>> a = funfunc()
>>> next(a) # no exit
init
enter
1
>>> next(a) # no exit
2
>>> next(a, 0) # exit because generator leaves the with-context inside the function
exit
0
或者如果手动删除:
>>> a = funfunc()
>>> next(a)
init
enter
1
>>> del a # exit because the variable holding the suspended generator is deleted.
exit
【讨论】:
接受了这个,因为添加的关于删除和范围的信息使它成为更完整的答案 您不必耗尽发电机,只需将其从with
中推进即可。至于删除它,生成器在垃圾回收时是 close
d,但这并不总是立即发生或根本不会发生(尤其是在 CPython 以外的任何东西上),即使在 @987654331 时,有问题的生成器也可能不会退出 with
@d.
@user2357112 但耗尽是一种可能性(因为它相当于return None
) - 即使没有立即退出上下文管理器,它最终也会在对象被销毁时退出(你有证据吗?对于不这样做的实现还是只是猜测?)。但是,您的最后一点很有趣:您所说的不退出 with
的“错误”生成器是什么意思?
@MSeifert: If the generator catches the GeneratorExit
exception and doesn't exit、finally
块和 __exit__
方法可能无法运行。【参考方案2】:
让我们根据经验进行测试。
class MyContextManager:
def __enter__(self):
pass
def __exit__(self, *args):
print "Context manager is exiting."
def f():
print("Entered Function.")
with MyContextManager() as m:
print("Entered with statement. Yielding...")
yield m
print("Yielded. About to exit with statement.")
print("Now outside of with statement.")
for x in f():
pass
输出:
C:\Users\Kevin\Desktop>test.py
Entered Function.
Entered with statement. Yielding...
Yielded. About to exit with statement.
Context manager is exiting.
Now outside of with statement.
“上下文管理器正在退出”消息出现在“About to exit with statement”消息之后,因此我们可以得出结论,yield
语句不会触发__exit__
方法。
【讨论】:
【参考方案3】:yield
在with
内不会触发__exit__
。生成器内部的控制流必须离开with
块,以便__exit__
触发;暂停生成器不算离开with
块。这在本质上类似于上下文切换到另一个线程不会触发__exit__
。
【讨论】:
【参考方案4】:简而言之:no,它会从到达yield
语句的那一刻起暂停该方法。如果您要求下一个元素,则执行剩余部分。
假设你写了result = tmp_csv_file()
什么都没做:所以甚至tmp_path='tmp.csv'
都没有被执行。
现在,如果您调用 next(result)
,Python 将开始评估函数直到它遇到第一个 yield
语句。所以它执行tmp_path = 'tmp.csv'
、open(..)
s 文件和__enter__
s 环境。它命中yield
语句并因此返回csv_file
。现在你可以用那个文件做任何你想做的事情。该文件将保持打开状态(只要您没有明确地close()
它),并且__exit__
将不会被调用。
如果您第二次调用next(result)
,Python 将继续寻找下一个yield
语句。因此它将__exit__
with
环境,并删除文件(os.remove(tmp_path)
)。然后它到达方法的结尾。这意味着我们完成了。因此next(..)
会抛出一个可迭代已用完的错误。
【讨论】:
以上是关于如果在“with”块内完成,“yield”会触发 __exit__ 函数吗?的主要内容,如果未能解决你的问题,请参考以下文章
您能否根据 around_action 过滤器中的 yield 块内发生的情况有条件地显示一条 Flash 消息?
当元素在敲除“with”绑定中时,jQuery自动完成不会触发动作