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 ff 绑定到 隐式 __enter__() 方法调用.

现在,如果 files 和 streamsfile.__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 现在绑定到上下文管理器而不是新的模拟对象。