С помощью выражений присваивания в Python 3.8, почему мы должны использовать `as` в`with`?
Теперь, когда принят PEP 572, Python 3.8 предназначен для использования выражений присваивания, поэтому мы можем использовать выражение присваивания в with
написать вполне естественно выглядящий
with f := open('file.txt'):
for l in f:
print(f)
вместо
with open('file.txt') as f:
for l in f:
print(f)
и это будет работать как раньше.
Какая польза as
Ключевое слово имеет с with
заявление в Python 3.8? Разве это не против дзен Python: "Должен быть один - и желательно только один - очевидный способ сделать это".?
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
теперь привязан к диспетчеру контекста вместо нового фиктивного объекта.