也许这是一个愚蠢的(实际上不是很实用)的问题,但我问的是因为我无法解决这个问题。
虽然研究了对上下文管理器的调用内的return
语句是否可以防止__exit__
被调用(不,它没有),但我发现在{ __exit__
块中的{1}}和finally
(例如:https://stackoverflow.com/a/9885287/3471881),原因是:
try/finally
将执行与以下相同的操作:
def test():
try:
return True
finally:
print("Good bye")
这确实帮助我了解了cm:s的工作原理,但经过一番尝试之后,我意识到如果我们返回的是东西而不是打印,这种类推是行不通的。
class MyContextManager:
def __enter__(self):
return self
def __exit__(self, *args):
print('Good bye')
def test():
with MyContextManager():
return True
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()
内部返回值?如果是,为什么?答案 0 :(得分:5)
是的。无法从__exit__
内部更改上下文的返回值。
如果使用return
语句退出了上下文,则无法使用context_manager.__exit__
更改返回值。这与try ... finally ...
子句不同,因为finally
中的代码仍属于父函数,而context_manager.__exit__
在其自身作用域中运行 / strong>
实际上,__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
都将导致控件跳出上下文。在第一个块中,上下文管理器的__exit__
方法返回True
,因此Python抑制了此异常,并且在REPL中未将其反映出来。在第二个块中,上下文管理器返回False
,因此Python让外部代码处理该异常,该异常由REPL打印出来。
答案 1 :(得分: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 {dt}s.")
print(f"All loops took {dt_mean}s on average.)