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

时间:2019-01-07 15:11:24

标签: python python-3.x

也许这是一个愚蠢的(实际上不是很实用)的问题,但我问的是因为我无法解决这个问题。

虽然研究了对上下文管理器的调用内的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()内部返回值?如果是,为什么?

2 个答案:

答案 0 :(得分:5)

是的。无法从__exit__内部更改上下文的返回值。

如果使用return语句退出了上下文,则无法使用context_manager.__exit__更改返回值。这与try ... finally ...子句不同,因为finally中的代码仍属于父函数,而context_manager.__exit__在其自身作用域中运行 / strong>

实际上,__exit__可以返回布尔值(TrueFalse),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.)