TL;DR: 尽管这两种结构之间可能没有明显的区别,但它们的行为并不相同。
在
with
语句中,几乎永远不需要使用
:=
,有时会非常错误。当存在疑惑时,在
with
块内需要使用托管对象时,始终使用
with ... as ...
。
在
with context_manager as managed
中,
managed
绑定到
context_manager.__enter__()
的返回值上,而在
with (managed := context_manager)
中,
managed
绑定到
context_manager
本身,且会丢弃
__enter__()
方法调用的返回值。对于打开的文件,这两者的行为几乎相同,因为它们的
__enter__
方法返回了
self
。
第一个摘录与
大致类似。
_mgr = (f := open('file.txt'))
_mgr.__enter__()
exc = True
try:
try:
BLOCK
except:
exc = False
if not _mgr.__exit__(*sys.exc_info()):
raise
finally:
if exc:
_mgr.__exit__(None, None, None)
而 as
形式将是
_mgr = open('file.txt')
_value = _mgr.__enter__()
exc = True
try:
try:
f = _value
BLOCK
except:
exc = False
if not _mgr.__exit__(*sys.exc_info()):
raise
finally:
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
是一个上下文管理器,将返回mock对象。其文档有以下示例:
>>> 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
现在绑定到上下文管理器而不是新的模拟对象。