对于Python 3.8中的赋值表达式,为什么我们需要在with中使用as?

时间:2018-07-17 15:44:57

标签: python python-3.8 python-assignment-expression

现在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中,as关键字与with语句有什么用?这不是违背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

但带括号的表达式有效。

1 个答案:

答案 0 :(得分:32)

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

您几乎永远不需要在:=语句中使用with,有时这是非常错误的。如有疑问,请在with ... as ...块中需要托管对象时始终使用with


with context_manager as managed中,managed绑定到context_manager.__enter__()返回值,而在with (managed := context_manager)中,managed被绑定到context_manager本身,并且__enter__()方法调用的返回值被丢弃。对于打开的文件,该行为几乎相同,因为它们的__enter__方法返回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的返回值,而with open(...) as ff绑定到隐式 { {1}}方法调用。

现在,如果是文件和流__enter__()如果成功将返回file.__enter__(),所以这两种方法的行为几乎是 相同-唯一的区别在于self引发异常。

赋值表达式通常可以代替__enter__起作用的事实具有欺骗性,因为as在许多类中返回的对象与{{1} }。在这种情况下,赋值表达式的工作方式有所不同:分配了上下文管理器,而不是托管对象。例如,unittest.mock.patch是一个上下文管理器,它将返回 mock 对象。其文档具有以下示例:

_mgr.__enter__()

现在,如果将其编写为使用赋值表达式,则行为将有所不同:

self

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