显式调用@contextmanager的__enter__与with语句的差异疑问
这个问题的核心在于**contextlib.contextmanager装饰器的工作原理**,以及显式调用上下文管理器方法时打破了正常的with语句生命周期。
先补全完整可复现的代码
首先把你提供的代码补全,方便理解场景:
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 @contextmanager def managed_connection(): # 用with语句管理Connection实例 with Connection() as conn: yield conn # 显式调用__enter__的场景 cm = managed_connection() conn_instance = cm.__enter__() print(f"Connection closed status: {conn_instance.closed}")
运行这段代码,你会看到输出:
Connection.__enter__ Connection.__exit__ Connection closed status: True
背后的原因解析
contextmanager装饰器的本质
这个装饰器会把你的生成器函数包装成一个实现了__enter__和__exit__方法的上下文管理器类:- 调用
__enter__时,它会执行生成器代码直到遇到yield语句,返回yield的值(也就是这里的conn)。 - 调用
__exit__时,它会继续执行生成器中yield之后的所有代码(包括with Connection()隐式的finally块)。
- 调用
显式调用
__enter__的问题
正常情况下,with语句会自动完成:调用__enter__→ 执行块内代码 → 调用__exit__。但当你**只显式调用__enter__而不调用__exit__**时,生成器对象会在后续被Python的垃圾回收机制回收。此时,生成器的close()方法会被触发,这会导致生成器中yield之后的代码(也就是with Connection()的退出逻辑,即调用Connection.__exit__)被强制执行。为什么
Connection.__exit__会提前执行?
在你的managed_connection函数里,with Connection() as conn本身就是一个上下文管理,它的退出逻辑(调用Connection.__exit__)被放在了生成器yield之后的隐式finally块中。当生成器被垃圾回收时,这部分代码会被触发,所以你会看到Connection.__exit__在__enter__返回实例后立刻被调用。
正确的使用方式
如果你想正常使用这个上下文管理器,应该用with语句,而不是显式调用__enter__和__exit__:
with managed_connection() as conn: print(f"Connection closed status inside with: {conn.closed}") print(f"Connection closed status outside with: {conn.closed}")
此时输出符合预期:
Connection.__enter__ Connection closed status inside with: False Connection.__exit__ Connection closed status outside with: True
内容的提问来源于stack exchange,提问作者rectalogic

