web-dev-qa-db-ja.com

Python 3.8の代入式で、 `with`で` as`を使用する必要があるのはなぜですか?

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キーワードは、Python 3.8のwithステートメントでどのように使用されますか?これは、Zen of Pythonに対してではありません:「それを行うための明白な方法は1つ(できれば1つだけ)あるはずです。」


この機能が最初に提案されたとき、代入式を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

ただし、括弧で囲まれた式は機能します。

26
Antti Haapala

TL; DR:2つの例の間に識別可能な違いはないとしても、動作は両方の構成要素で同じではありません。

withステートメントで_:=_を必要とすることはほとんどないはずです。疑問がある場合は、withブロック内の管理対象オブジェクトが必要な場合は、常に_with ... as ..._を使用してください。


_with context_manager as managed_では、managedcontext_manager.__enter__()戻り値にバインドされますが、with (managed := context_manager)ではmanagedは_context_manager_自体にバインドされており、__enter__()メソッド呼び出しの戻り値はdiscardedです。 ___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(...))fopenの戻り値に設定しますが、with open(...) as ffimplicit__enter__()メソッド呼び出し。

現在、ファイルとストリームの場合、file.__enter__()は成功するとselfを返すので、これらの2つのアプローチはほとんど同じです-唯一の違いは、___enter___が例外をスローする場合です。

_mgr.__enter__()distinctであるオブジェクトを返す多くのクラスがあるため、代入式がasの代わりに機能することが多いという事実は誤解を招きます。 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_が、新しいモックオブジェクトではなく、コンテキストマネージャーにバインドされるようになりました。

33
Antti Haapala