在 @contextmanager 上显式调用 __enter__ 与 "with" 语句之间无法解释的区别
Unexplained difference between explicitly invoking __enter__ on @contextmanager vs "with" statement
我有一个上下文管理器类型 (Connection
) 和一个 @contextmanager
修饰函数,它从 with
语句中生成该类型。
如果我在修饰函数上显式调用 __enter__
,则 __exit__
在 Connection
返回之前被调用。
这是代码:
from __future__ import print_function
from contextlib import contextmanager
class Connection(object):
def __init__(self):
self.closed = False
def __enter__(self):
print('Connection.__enter__')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('Connection.__exit__')
self.closed = True
return False
def __repr__(self):
return "{}(closed={})".format(self.__class__.__name__, self.closed)
@contextmanager
def connect():
with Connection() as c:
print('connect yielding connection')
yield c
print('connect yielded connection')
def context():
print("connect explicit call to __enter__")
c = connect().__enter__()
print("got connection via __enter__", c)
print()
print("connect implicit call to __enter__ via with")
with connect() as c:
print("got connection in 'with'", c)
print("connection after 'with'", c)
print()
print("Connection explicit call to __enter__")
c = Connection().__enter__()
print("got connection via __enter__", c)
print()
print("Connection implicit call to __enter__ via with")
with Connection() as c:
print("got connection in with", c)
print("connection after 'with'", c)
if __name__ == "__main__":
context()
当 运行 输出:
connect explicit call to __enter__
Connection.__enter__
connect yielding connection
Connection.__exit__
got connection via __enter__ Connection(closed=True)
connect implicit call to __enter__ via with
Connection.__enter__
connect yielding connection
got connection in 'with' Connection(closed=False)
connect yielded connection
Connection.__exit__
connection after 'with' Connection(closed=True)
Connection explicit call to __enter__
Connection.__enter__
got connection via __enter__ Connection(closed=False)
Connection implicit call to __enter__ via with
Connection.__enter__
got connection in with Connection(closed=False)
Connection.__exit__
connection after 'with' Connection(closed=True)
比较以 "connect explicit call to __enter__" 和 "connect implicit call to __enter__ via with" 开头的序列。
当我在 @contextmanager
修饰函数上显式调用 __enter__
时,为什么在连接返回给我之前调用 Connection.__exit__
?为什么 "connect yielded connection" 从未打印过 - 这意味着它仍在 yield 语句中并且尚未离开 with
块 - 为什么 __exit__
被调用?
您丢弃了上下文管理器,因此生成器对象符合回收条件。生成器的 __del__
* 自动调用生成器上的 close
,它在最后一个 yield
处引发 GeneratorExit
,结束 with Connection() as c
块并触发 c.__exit__
.
*从技术上讲,在 Python 2 上,这实际上发生在 lower-level 清理例程中,而不是 __del__
.
我有一个上下文管理器类型 (Connection
) 和一个 @contextmanager
修饰函数,它从 with
语句中生成该类型。
如果我在修饰函数上显式调用 __enter__
,则 __exit__
在 Connection
返回之前被调用。
这是代码:
from __future__ import print_function
from contextlib import contextmanager
class Connection(object):
def __init__(self):
self.closed = False
def __enter__(self):
print('Connection.__enter__')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print('Connection.__exit__')
self.closed = True
return False
def __repr__(self):
return "{}(closed={})".format(self.__class__.__name__, self.closed)
@contextmanager
def connect():
with Connection() as c:
print('connect yielding connection')
yield c
print('connect yielded connection')
def context():
print("connect explicit call to __enter__")
c = connect().__enter__()
print("got connection via __enter__", c)
print()
print("connect implicit call to __enter__ via with")
with connect() as c:
print("got connection in 'with'", c)
print("connection after 'with'", c)
print()
print("Connection explicit call to __enter__")
c = Connection().__enter__()
print("got connection via __enter__", c)
print()
print("Connection implicit call to __enter__ via with")
with Connection() as c:
print("got connection in with", c)
print("connection after 'with'", c)
if __name__ == "__main__":
context()
当 运行 输出:
connect explicit call to __enter__
Connection.__enter__
connect yielding connection
Connection.__exit__
got connection via __enter__ Connection(closed=True)
connect implicit call to __enter__ via with
Connection.__enter__
connect yielding connection
got connection in 'with' Connection(closed=False)
connect yielded connection
Connection.__exit__
connection after 'with' Connection(closed=True)
Connection explicit call to __enter__
Connection.__enter__
got connection via __enter__ Connection(closed=False)
Connection implicit call to __enter__ via with
Connection.__enter__
got connection in with Connection(closed=False)
Connection.__exit__
connection after 'with' Connection(closed=True)
比较以 "connect explicit call to __enter__" 和 "connect implicit call to __enter__ via with" 开头的序列。
当我在 @contextmanager
修饰函数上显式调用 __enter__
时,为什么在连接返回给我之前调用 Connection.__exit__
?为什么 "connect yielded connection" 从未打印过 - 这意味着它仍在 yield 语句中并且尚未离开 with
块 - 为什么 __exit__
被调用?
您丢弃了上下文管理器,因此生成器对象符合回收条件。生成器的 __del__
* 自动调用生成器上的 close
,它在最后一个 yield
处引发 GeneratorExit
,结束 with Connection() as c
块并触发 c.__exit__
.
*从技术上讲,在 Python 2 上,这实际上发生在 lower-level 清理例程中,而不是 __del__
.