[英]Returning value when exiting python context manager
也許這是一個愚蠢的(實際上不是很實用)的問題,但我問它是因為我無法理解它。
雖然研究如果return
到一個上下文管理器的調用內聲明將阻止__exit__
被稱為(沒有也),我發現它似乎共同做出的類比__exit__
並finally
在try/finally
塊(例如這里: https : //stackoverflow.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__
似乎__exit__
不會返回:
class MyContextManager:
def __enter__(self):
return self
def __exit__(self, *args):
return False
def test():
with MyContextManager():
return True
test()
--> True
這讓我想到,也許您實際上無法在__exit__
內返回任何__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__
上下文的返回值。
如果使用return
語句退出上下文,則無法使用context_manager.__exit__
更改返回值。 這與try ... finally ...
子句不同,因為finally
的代碼仍然屬於父函數,而context_manager.__exit__
在其自己的作用context_manager.__exit__
運行。
事實上, __exit__
可以返回一個布爾值( True
或False
),它會被 Python 理解。 它告訴 Python 是否應該抑制退出上下文(如果有)的異常(不傳播到上下文之外)。
請參閱__exit__
返回值含義的__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 打印出來。
解決方法是將結果存儲在一個屬性中而不是返回它,然后再訪問它。 也就是說,如果您打算在打印之外使用該值。
例如,以這個簡單的上下文管理器為例:
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
對象是不能訪問了,因為范圍的兩端。 我們需要按照這里的描述修改類,並將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.)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.