Необъяснимая разница между явным вызовом __enter__ в @contextmanager и выражении "with"
У меня есть тип менеджера контекста (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)
Сравните последовательность, начинающуюся с "подключить явный вызов к __enter__", чтобы "подключить неявный вызов к __enter__ через с".
Когда я явно призываю __enter__
на @contextmanager
украшенная функция, почему Connection.__exit__
звонил до того как мне вернули соединение? И почему "соединение с выходом" никогда не печатается - это означает, что оно все еще находится в операторе yield и еще не покинуло with
блок - так почему __exit__
называется?
1 ответ
Вы отказались от диспетчера контекста, поэтому объект генератора получил право на восстановление. Генератор __del__
* автоматически звонит close
на генераторе, который поднимает GeneratorExit
в точке последнего yield
, заканчивая with Connection() as c
блокировка и запуск c.__exit__
,
* технически, на Python 2, это на самом деле происходит в процедуре очистки более низкого уровня, чем __del__
,