在“with”语句中调用构造函数

Ian*_*son 51 python class with-statement python-3.x

我有以下代码:

class Test:

    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print(f'entering {self.name}')

    def __exit__(self, exctype, excinst, exctb) -> bool:
        print(f'exiting {self.name}')
        return True

with Test('first') as test:
    print(f'in {test.name}')

test = Test('second')
with test:
    print(f'in {test.name}')
Run Code Online (Sandbox Code Playgroud)

运行它会产生以下输出:

class Test:

    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print(f'entering {self.name}')

    def __exit__(self, exctype, excinst, exctb) -> bool:
        print(f'exiting {self.name}')
        return True

with Test('first') as test:
    print(f'in {test.name}')

test = Test('second')
with test:
    print(f'in {test.name}')
Run Code Online (Sandbox Code Playgroud)

但我预计它会产生:

entering first
exiting first
entering second
in second
exiting second
Run Code Online (Sandbox Code Playgroud)

为什么我的第一个示例中的代码没有被调用?

Sil*_*olo 60

__enter__方法应该返回上下文对象。with ... as ...使用 的返回值__enter__来确定要给您什么对象。由于您__enter__没有返回任何内容,因此它隐式返回None,因此test也是None

with Test('first') as test:
    print(f'in {test.name}')

test = Test('second')
with test:
    print(f'in {test.name}')
Run Code Online (Sandbox Code Playgroud)

test没有。然后test.name就是一个错误。该错误被引发,因此Test('first').__exit__被调用。__exit__回报True,这表明错误已被处理(本质上,你的__exit__行为就像一个except块),因此代码在第一个with块之后继续,因为你告诉Python一切都很好。

考虑

def __enter__(self):
    print(f'entering {self.name}')
    return self
Run Code Online (Sandbox Code Playgroud)

您也可能会考虑不返回True__exit__除非您确实打算无条件抑制块中的所有错误(并且完全理解抑制其他程序员的错误以及各种系统信号的后果KeyboardInterruptStopIteration

  • @Barmar:有时`__enter__`返回`None`,因为`as`目标确实应该设置为`None`。如果“None”的返回值是特殊情况,那么就会破坏真正需要“None”的情况。节省一些“return self”行并不值得。 (5认同)
  • @Barmar:不。`with`调用上下文管理器的`__exit__`,而不是`__enter__`返回值的`__exit__`。 (4认同)
  • 有趣的是,最初的 PEP-310 提案中“with”的表现与 OP 预期的一样。这在 PEP-343 中进行了更改。我想知道为什么当“__enter__”返回“None”时他们不将该对象设置为默认上下文,因为这是很常见的情况。 (2认同)

jua*_*aga 10

问题是你的__enter__方法返回None. 因此,test被指定为None

然后您尝试访问(None).name,这会引发错误。由于您的__exit__方法始终返回True,因此它将抑制任何错误。根据文档

从此方法返回 true 值将导致 with 语句抑制异常并继续执行紧随 with 语句之后的语句。