退出python上下文管理器时返回值
Posted
技术标签:
【中文标题】退出python上下文管理器时返回值【英文标题】:Returning value when exiting python context manager 【发布时间】:2019-06-02 06:29:25 【问题描述】:也许这是一个愚蠢(而且确实不是很实际)的问题,但我之所以问这个问题是因为我无法理解它。
在研究调用上下文管理器中的 return
语句是否会阻止调用 __exit__
(不,不会)时,我发现在 __exit__
和 @ 之间进行类比似乎很常见987654325@ 在 try/finally
块中(例如这里:https://***.com/a/9885287/3471881)因为:
def test():
try:
return True
finally:
print("Good bye")
将执行相同的操作:
class MyContextManager:
def __enter__(self):
return self
def __exit__(self, *args):
print('Good bye')
def test():
with MyContextManager():
return True
这确实帮助我理解了 cm:s 的工作原理,但在玩了一会儿之后,我意识到如果我们返回一些东西而不是打印,这个类比就行不通了。
def test():
try:
return True
finally:
return False
test()
--> False
虽然__exit__
似乎根本不会回来:
class MyContextManager:
def __enter__(self):
return self
def __exit__(self, *args):
return False
def test():
with MyContextManager():
return True
test()
--> True
这让我想到,也许您实际上无法在 __exit__
中返回任何内容,但您可以:
class MyContextManager:
def __enter__(self):
return self
def __exit__(self, *args):
return self.last_goodbye()
def last_goodbye(self):
print('Good bye')
def test():
with MyContextManager():
return True
test()
--> Good bye
--> True
请注意,如果我们在 test()
函数中不返回任何内容,这并不重要。
这引出了我的问题:
是否不可能从__exit__
内部返回值,如果是,为什么?
【问题讨论】:
请注意__exit__
的返回值有一个定义的含义:如果__exit__
返回True
,那么在with
上下文中抛出的任何异常都将被抑制并且不会传播到外部with
块。
【参考方案1】:
是的。无法从__exit__
内部更改上下文的返回值。
如果使用return
语句退出上下文,则不能使用context_manager.__exit__
更改返回值。这与try ... finally ...
子句不同,因为finally
中的代码仍然属于父函数,而context_manager.__exit__
在自己的范围内运行
.
其实__exit__
可以返回一个布尔值(True
或False
),Python会理解。它告诉 Python 是否应该抑制退出上下文(如果有)的异常(不传播到上下文之外)。
看这个例子__exit__
的返回值的含义:
>>> class MyContextManager:
... def __init__(self, suppress):
... self.suppress = suppress
...
... def __enter__(self):
... return self
...
... def __exit__(self, exc_type, exc_obj, exc_tb):
... return self.suppress
...
>>> with MyContextManager(True): # suppress exception
... raise ValueError
...
>>> with MyContextManager(False): # let exception pass through
... raise ValueError
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError
>>>
在上面的例子中,ValueError
s 都会导致控件跳出上下文。在第一个块中,上下文管理器的__exit__
方法返回True
,因此Python 抑制了这个异常并且它不会在REPL 中反映。在第二个块中,上下文管理器返回False
,因此 Python 让外部代码处理异常,该异常由 REPL 打印出来。
【讨论】:
这里的第一句话我真的没看懂。如果我在contextManager.__exit__
内返回一个值,但在 with contextManager(): ...
内不返回 - 我尝试时仍然不会得到返回值。
@user3471881 那是因为__exit__
在自己的范围内运行,所以不会影响上下文的返回值。请注意,如果控件到达函数末尾,函数会隐式返回 None
。【参考方案2】:
解决方法是将结果存储在属性中而不是返回它,然后再访问它。也就是说,如果您打算在不止一次打印中使用该值。
以这个简单的上下文管理器为例:
class time_this_scope():
"""Context manager to measure how much time was spent in the target scope."""
def __init__(self, allow_print=False):
self.t0 = None
self.dt = None
self.allow_print = allow_print
def __enter__(self):
self.t0 = time.perf_counter()
def __exit__(self, type=None, value=None, traceback=None):
self.dt = (time.perf_counter() - self.t0) # Store the desired value.
if self.allow_print is True:
print(f"Scope took self.dt*1000: 0.1f milliseconds.")
可以这样使用:
with time_this_scope(allow_print=True):
time.sleep(0.100)
>>> Scope took 100 milliseconds.
或者像这样:
timer = time_this_scope()
with timer:
time.sleep(0.100)
dt = timer.dt
不像如下所示,因为随着作用域的结束,timer
对象不再可访问。我们需要修改类as described here,并将return self
的值添加到__enter__
。修改前会报错:
with time_this_scope() as timer:
time.sleep(0.100)
dt = timer.dt
>>> AttributeError: 'NoneType' object has no attribute 'dt'
最后,这里是一个简单的使用例子:
"""Calculate the average time spent sleeping."""
import numpy as np
import time
N = 100
dt_mean = 0
for n in range(N)
timer = time_this_scope()
with timer:
time.sleep(0.001 + np.random.rand()/1000) # 1-2 ms per loop.
dt = timer.dt
dt_mean += dt/N
print(f"Loop n+1/N took dts.")
print(f"All loops took dt_means on average.)
【讨论】:
以上是关于退出python上下文管理器时返回值的主要内容,如果未能解决你的问题,请参考以下文章