Python 3.8中的赋值表达式,为什么我们需要在with中使用as?
With assignment expressions in Python 3.8, why do we need to use `as` in `with`?
既然PEP 572已经接受了,Python3.8注定会有赋值表达式,所以我们可以在[=16中使用赋值表达式=],即
with (f := open('file.txt')):
for l in f:
print(f)
而不是
with open('file.txt') as f:
for l in f:
print(f)
它会像以前一样工作。
as
关键字与 Python 3.8 中的 with
语句有什么用?这不是违背了Python的禅:"There should be one -- and preferably only one -- obvious way to do it."?
最初提出该特性时,并没有明确规定赋值表达式是否应该在with
中加括号,
with f := open('file.txt'):
for l in f:
print(f)
可以。但是,在 Python 3.8a0 中,
with f := open('file.txt'):
for l in f:
print(f)
会导致
File "<stdin>", line 1
with f := open('file.txt'):
^
SyntaxError: invalid syntax
但是带括号的表达式有效。
TL;DR:两种结构的行为并不相同,即使这两个示例之间没有明显差异。
你几乎从不需要在 with
语句中使用 :=
,有时这是非常错误的。如有疑问,当您需要 with
块中的托管对象时,请始终使用 with ... as ...
。
在with context_manager as managed
中,managed
绑定到context_manager.__enter__()
的return值,而在[=21=中],managed
绑定到 context_manager
本身,__enter__()
方法调用的 return 值被 丢弃 。对于打开的文件,行为 几乎 相同,因为它们的 __enter__
方法 returns self
.
第一个摘录是roughly analogous to
_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
的 return 值,而 with open(...) as f
将 f
绑定到 隐式 __enter__()
方法调用.
现在,如果 files 和 streams,file.__enter__()
将 return self
如果成功,那么这两个的行为方法几乎相同——唯一的区别是__enter__
抛出异常。
赋值表达式通常代替 as
起作用的事实具有欺骗性,因为有许多 类 其中 _mgr.__enter__()
return 对象是 与 self
不同 。在那种情况下,赋值表达式的工作方式不同:上下文管理器被赋值,而不是被管理对象。例如 unittest.mock.patch
是一个上下文管理器,它将 return 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
现在绑定到上下文管理器而不是新的模拟对象。
既然PEP 572已经接受了,Python3.8注定会有赋值表达式,所以我们可以在[=16中使用赋值表达式=],即
with (f := open('file.txt')):
for l in f:
print(f)
而不是
with open('file.txt') as f:
for l in f:
print(f)
它会像以前一样工作。
as
关键字与 Python 3.8 中的 with
语句有什么用?这不是违背了Python的禅:"There should be one -- and preferably only one -- obvious way to do it."?
最初提出该特性时,并没有明确规定赋值表达式是否应该在with
中加括号,
with f := open('file.txt'):
for l in f:
print(f)
可以。但是,在 Python 3.8a0 中,
with f := open('file.txt'):
for l in f:
print(f)
会导致
File "<stdin>", line 1
with f := open('file.txt'):
^
SyntaxError: invalid syntax
但是带括号的表达式有效。
TL;DR:两种结构的行为并不相同,即使这两个示例之间没有明显差异。
你几乎从不需要在 with
语句中使用 :=
,有时这是非常错误的。如有疑问,当您需要 with
块中的托管对象时,请始终使用 with ... as ...
。
在with context_manager as managed
中,managed
绑定到context_manager.__enter__()
的return值,而在[=21=中],managed
绑定到 context_manager
本身,__enter__()
方法调用的 return 值被 丢弃 。对于打开的文件,行为 几乎 相同,因为它们的 __enter__
方法 returns self
.
第一个摘录是roughly analogous to
_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
的 return 值,而 with open(...) as f
将 f
绑定到 隐式 __enter__()
方法调用.
现在,如果 files 和 streams,file.__enter__()
将 return self
如果成功,那么这两个的行为方法几乎相同——唯一的区别是__enter__
抛出异常。
赋值表达式通常代替 as
起作用的事实具有欺骗性,因为有许多 类 其中 _mgr.__enter__()
return 对象是 与 self
不同 。在那种情况下,赋值表达式的工作方式不同:上下文管理器被赋值,而不是被管理对象。例如 unittest.mock.patch
是一个上下文管理器,它将 return 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
现在绑定到上下文管理器而不是新的模拟对象。