web-dev-qa-db-fra.com

Avec des expressions d'affectation dans Python 3.8, pourquoi devons-nous utiliser `as` dans` with`?

Maintenant que PEP 572 a été accepté, Python 3.8 est destiné à avoir des expressions d'affectation , afin que nous puissions utiliser une expression d'affectation dans with, c'est-à-dire.

with (f := open('file.txt')):
    for l in f:
        print(f)

au lieu de

with open('file.txt') as f:
    for l in f:
        print(f)

et cela fonctionnerait comme avant.

À quoi sert le mot clé as avec l'instruction with dans Python 3.8? N'est-ce pas contre le Zen de Python: "Il devrait y avoir une - et de préférence une seule - manière évidente de le faire." ?


Lorsque la fonctionnalité a été initialement proposée, il n'était pas clairement spécifié si l'expression d'affectation devait être entre parenthèses dans with et que

with f := open('file.txt'):
    for l in f:
        print(f)

pourrait fonctionner. Cependant, dans Python 3.8a0,

with f := open('file.txt'):
    for l in f:
        print(f)

provoquera

  File "<stdin>", line 1
    with f := open('file.txt'):
           ^
SyntaxError: invalid syntax

mais l'expression entre parenthèses fonctionne.

26
Antti Haapala

TL; DR : Le comportement n'est pas le même pour les deux constructions, même s'il n'y aurait pas de différences perceptibles entre les 2 exemples.

Vous ne devriez presque jamais avoir besoin de := Dans une instruction with, et parfois c'est très faux. En cas de doute, utilisez toujours with ... as ... Lorsque vous avez besoin de l'objet géré dans le bloc with.


Dans with context_manager as managed, managed est lié à valeur de retour de context_manager.__enter__(), tandis que dans with (managed := context_manager), managed est lié au context_manager lui-même et la valeur de retour de l'appel de méthode __enter__() est jeté. Le comportement est presque identique pour les fichiers ouverts, car leur méthode __enter__ Renvoie self.

Le premier extrait est à peu près analogue à

_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)

alors que la forme as serait

_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)

c'est-à-dire que with (f := open(...)) définirait f à la valeur de retour de open, tandis que with open(...) as f lie f à la valeur de retour de = implicite__enter__() appel de méthode.

Maintenant, dans le cas de fichiers et flux , file.__enter__() renverra self s'il réussit, donc le comportement de ces deux approches est presque la même chose - la seule différence est dans le cas où __enter__ lève une exception.

Le fait que les expressions d'affectation fonctionnent souvent au lieu de as est trompeur, car il existe de nombreuses classes où _mgr.__enter__() renvoie un objet qui est distinct de self. Dans ce cas, une expression d'affectation fonctionne différemment: le gestionnaire de contexte est affecté, au lieu de objet géré. Par exemple unittest.mock.patch est un gestionnaire de contexte qui renverra l'objet mock. La documentation correspondante contient l'exemple suivant:

>>> 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

Maintenant, s'il devait être écrit pour utiliser une expression d'affectation, le comportement serait différent:

>>> 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 Est désormais lié au gestionnaire de contexte au lieu du nouvel objet factice.

33
Antti Haapala