27

现在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关键字与withPython 3.8 中的语句有什么用?这不是违背 Python 的禅宗吗:“应该有一种——最好只有一种——明显的方式来做到这一点。” ?


最初提出该功能时,并没有明确指定赋值表达式是否应该括起来,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

但括号内的表达式有效。

4

1 回答 1

41

TL;DR:这两种构造的行为并不相同,即使这两个示例之间没有明显的差异。

你应该几乎从不需要:=在一个with声明中,有时它是非常错误的。如有疑问,请始终with ... as ...在您需要块内的托管对象时使用with


in with context_manager as managed,managed绑定到 的返回值context_manager.__enter__()而 in 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)

iewith (f := open(...))将设置f为的返回值open,而with open(...) as f绑定f隐式 __enter__()方法调用的返回值。

现在,在文件和流的情况下,如果成功file.__enter__()将返回,因此这两种方法的行为几乎相同 - 唯一的区别在于引发异常的事件。self__enter__

赋值表达式通常会起作用的事实具有欺骗性as,因为有许多类_mgr.__enter__()返回的对象与. 在这种情况下,赋值表达式的工作方式不同:分配上下文管理器,而不是托管对象。例如,一个将返回模拟对象的上下文管理器。它的文档有以下示例:selfunittest.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现在绑定到上下文管理器而不是新的模拟对象。

于 2018-07-17T15:44:57.190 回答