簡體   English   中英

退出python上下文管理器時的返回值

[英]Returning value when exiting python context manager

也許這是一個愚蠢的(實際上不是很實用)的問題,但我問它是因為我無法理解它。

雖然研究如果return到一個上下文管理器的調用內聲明將阻止__exit__被稱為(沒有也),我發現它似乎共同做出的類比__exit__finallytry/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__可以返回一個布爾值( TrueFalse ),它會被 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM