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