在 Python 3.8 中,使用赋值表达式时,为什么我们需要在 `with` 语句中使用 `as`?

28

现在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)

并且它将像以前一样工作。

在Python 3.8中,with语句与as关键字有什么用途?这不违背了Python之禅的原则:“做一件事应该有且只有一种明显的方法。”吗?


当最初提出此功能时,并没有明确指定在with中赋值表达式是否应该加括号,而那时的提案也没有提到as关键字。因此,为了保持向后兼容性,as关键字被引入以解决这个问题。

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

但是括号表达式起作用。

1个回答

42
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')) # `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是一个上下文管理器,将返回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现在绑定到上下文管理器而不是新的模拟对象。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接