TL;DR: обе конструкции ведут себя по-разному, хотя между двумя примерами не будет заметных различий.
Вы почти никогда не должны нуждаться в :=
в операторе with
, а иногда это очень неправильно. В случае сомнений всегда используйте with ... as ...
, если вам нужен управляемый объект в блоке with
.
В with context_manager as managed
managed
привязывается к возвращаемому значению функции context_manager.__enter__()
, тогда как в with (managed := context_manager)
managed
привязывается к самому context_manager
, а возвращаемое значение вызова метода __enter__()
отбрасывается. Поведение для открытых файлов почти идентично, потому что их метод __enter__
возвращает self
.
Первая выдержка: примерно аналогично
_mgr = (f := open('file.txt')) # `f` is assigned here, even if `__enter__` fails
_mgr.__enter__() # the return value is discarded
exc = True
try:
try:
BLOCK
except:
# The exceptional case is handled here
exc = False
if not _mgr.__exit__(*sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
_mgr.__exit__(None, None, None)
тогда как форма as
будет
_mgr = open('file.txt') #
_value = _mgr.__enter__() # the return value is kept
exc = True
try:
try:
f = _value # here f is bound to the return value of __enter__
# and therefore only when __enter__ succeeded
BLOCK
except:
# The exceptional case is handled here
exc = False
if not _mgr.__exit__(*sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
_mgr.__exit__(None, None, None)
т. е. with (f := open(...))
присвоит f
возвращаемое значение open
, тогда как with open(...) as f
привязывает f
к возвращаемому значению неявного вызова метода __enter__()
.
Теперь, в случае с файлами и потоками, file.__enter__()
вернет self
в случае успеха, поэтому поведение этих двух подходов почти одинаково — единственная разница заключается в событии что __enter__
выдает исключение.
Тот факт, что вместо as
часто работают выражения присваивания, обманчив, поскольку существует множество классов, в которых _mgr.__enter__()
возвращает объект, отличный от self
. В этом случае выражение присваивания работает иначе: вместо управляемого объекта назначается менеджер контекста. Например, unittest.mock.patch
— это контекстный менеджер, который возвращает макет объекта. В документации к нему есть следующий пример:
>>> thing = object()
>>> with patch('__main__.thing', new_callable=NonCallableMock) as mock_thing:
... assert thing is mock_thing
... thing()
...
Traceback (most recent call last):
...
TypeError: 'NonCallableMock' object is not callable
Теперь, если бы это было написано с использованием выражения присваивания, поведение было бы другим:
>>> thing = object()
>>> with (mock_thing := patch('__main__.thing', new_callable=NonCallableMock)):
... assert thing is mock_thing
... thing()
...
Traceback (most recent call last):
...
AssertionError
>>> thing
<object object at 0x7f4aeb1ab1a0>
>>> mock_thing
<unittest.mock._patch object at 0x7f4ae910eeb8>
mock_thing
теперь привязан к диспетчеру контекста, а не к новому фиктивному объекту.
person
Antti Haapala
schedule
17.07.2018