Ответ 1
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
теперь связан с менеджером контекста, а не с новым фиктивным объектом.