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

use*_*881 8 python python-3.x

也许这是一个愚蠢的(实际上并不是很实际的)问题,但我问它是因为我无法绕过它.

在研究return调用上下文管理器中的语句是否会阻止__exit__被调用时(不是没有),我发现在块之间__exit__和块之间进行类比似乎很常见(例如:https:// stackoverflow .com/a/9885287/3471881)因为:finallytry/finally

def test():
    try:
        return True
    finally:
        print("Good bye")
Run Code Online (Sandbox Code Playgroud)

将执行相同的:

class MyContextManager:

    def __enter__(self):
        return self

    def __exit__(self, *args):
        print('Good bye')

def test():
    with MyContextManager():
        return True
Run Code Online (Sandbox Code Playgroud)

这真的帮助我理解cm:s是如何工作的,但是在玩了一下之后我意识到如果我们返回的东西而不是打印,这个类比就不会起作用.

def test():
    try:
        return True
    finally:
        return False
test()    
--> False
Run Code Online (Sandbox Code Playgroud)

虽然__exit__看似根本不会回归:

class MyContextManager:

    def __enter__(self):
        return self

    def __exit__(self, *args):
        return False

def test():
    with MyContextManager():
        return True

test()
--> True
Run Code Online (Sandbox Code Playgroud)

这让我想到也许你实际上不能归还任何东西__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
Run Code Online (Sandbox Code Playgroud)

请注意,如果我们不在test()函数内部返回任何内容并不重要.

这引出了我的问题:

  • 是否无法从内部返回值__exit__,如果是,为什么?

iBu*_*Bug 8

是.从内部改变上下文的返回值是不可能的__exit__.

如果使用return语句退出上下文,则不能使用您的语句更改返回值context_manager.__exit__.这与try ... finally ...子句不同,因为代码finally仍然属于父函数,而在自己的范围内context_manager.__exit__运行 .

实际上,__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
>>>
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,两个ValueErrors都会导致控件跳出上下文.在第一个块中,__exit__上下文管理器的方法返回True,因此Python会抑制此异常,并且它在REPL中没有反射.在第二个块中,上下文管理器返回False,因此Python让外部代码处理异常,由REPL打印出来.

  • @ user3471881这是因为__exit__在其自己的范围内运行,因此不会影响上下文的返回值。请注意,如果控件到达函数末尾,则该函数隐式返回“ None”。 (2认同)

Gui*_*ute 5

解决方法是将结果存储在属性中而不是返回它,并稍后访问它。也就是说,如果您打算在打印之外使用该值。

例如,采用这个简单的上下文管理器:

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.")
Run Code Online (Sandbox Code Playgroud)

可以这样使用:

with time_this_scope(allow_print=True):
    time.sleep(0.100)

>>> Scope took 100 milliseconds.
Run Code Online (Sandbox Code Playgroud)

或者像这样:

timer = time_this_scope()
with timer:
    time.sleep(0.100)
dt = timer.dt 
Run Code Online (Sandbox Code Playgroud)

与下面显示的不同,因为随着作用域结束,该对象timer将不再可访问。我们需要按照此处所述return self修改该类并向__enter__. 修改之前,会报错:

with time_this_scope() as timer:
    time.sleep(0.100)
dt = timer.dt 

>>> AttributeError: 'NoneType' object has no attribute 'dt'
Run Code Online (Sandbox Code Playgroud)

最后,这是一个简单的使用示例:

"""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.)
Run Code Online (Sandbox Code Playgroud)