Différencier les origines possibles des exceptions soulevées à partir d'une with
déclaration composée
La with
distinction entre les exceptions qui se produisent dans une instruction est délicate car elles peuvent provenir de différents endroits. Des exceptions peuvent être levées à partir de l'un des endroits suivants (ou des fonctions qui y sont appelées):
ContextManager.__init__
ContextManager.__enter__
- le corps du
with
ContextManager.__exit__
Pour plus de détails, consultez la documentation sur les types de gestionnaire de contexte .
Si nous voulons faire la distinction entre ces différents cas, il ne suffit pas d' envelopper le with
dans un try .. except
. Prenons l'exemple suivant (en utilisant ValueError
comme exemple, mais bien sûr, il pourrait être remplacé par tout autre type d'exception):
try:
with ContextManager():
BLOCK
except ValueError as err:
print(err)
Ici, la except
capture des exceptions provenant de chacun des quatre endroits différents et ne permet donc pas de les distinguer. Si nous déplaçons l'instanciation de l'objet gestionnaire de contexte en dehors de with
, nous pouvons distinguer entre __init__
et BLOCK / __enter__ / __exit__
:
try:
mgr = ContextManager()
except ValueError as err:
print('__init__ raised:', err)
else:
try:
with mgr:
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except ValueError as err:
# At this point we still cannot distinguish between exceptions raised from
# __enter__, BLOCK, __exit__ (also BLOCK since we didn't catch ValueError in the body)
pass
En fait, cela a simplement aidé la __init__
partie, mais nous pouvons ajouter une variable sentinelle supplémentaire pour vérifier si le corps de la with
machine a commencé à s'exécuter (c'est-à-dire en faisant la différence entre __enter__
les autres):
try:
mgr = ContextManager() # __init__ could raise
except ValueError as err:
print('__init__ raised:', err)
else:
try:
entered_body = False
with mgr:
entered_body = True # __enter__ did not raise at this point
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except ValueError as err:
if not entered_body:
print('__enter__ raised:', err)
else:
# At this point we know the exception came either from BLOCK or from __exit__
pass
La partie délicate est de faire la différence entre les exceptions provenant de BLOCK
et __exit__
parce qu'une exception qui échappe au corps du with
sera transmise à __exit__
qui pourra décider comment la gérer (voir la documentation ). Si toutefois se __exit__
lève, l'exception d'origine sera remplacée par la nouvelle. Pour traiter ces cas, nous pouvons ajouter une except
clause générale dans le corps de la with
pour stocker toute exception potentielle qui aurait autrement échappé inaperçue et la comparer avec celle capturée le plus à l'extérieur except
plus tard - s'ils sont les mêmes, cela signifie que l'origine était BLOCK
ou __exit__
dans le cas contraire (dans le cas où il __exit__
supprime l'exception en renvoyant une valeur vraieexcept
ne sera tout simplement pas exécuté).
try:
mgr = ContextManager() # __init__ could raise
except ValueError as err:
print('__init__ raised:', err)
else:
entered_body = exc_escaped_from_body = False
try:
with mgr:
entered_body = True # __enter__ did not raise at this point
try:
BLOCK
except TypeError: # catching another type (which we want to handle here)
pass
except Exception as err: # this exception would normally escape without notice
# we store this exception to check in the outer `except` clause
# whether it is the same (otherwise it comes from __exit__)
exc_escaped_from_body = err
raise # re-raise since we didn't intend to handle it, just needed to store it
except ValueError as err:
if not entered_body:
print('__enter__ raised:', err)
elif err is exc_escaped_from_body:
print('BLOCK raised:', err)
else:
print('__exit__ raised:', err)
Approche alternative utilisant le formulaire équivalent mentionné dans le PEP 343
PEP 343 - L'instruction "with" spécifie une version équivalente "non avec" de l' with
instruction. Ici, nous pouvons facilement envelopper les différentes parties try ... except
et ainsi différencier les différentes sources d'erreur potentielles:
import sys
try:
mgr = ContextManager()
except ValueError as err:
print('__init__ raised:', err)
else:
try:
value = type(mgr).__enter__(mgr)
except ValueError as err:
print('__enter__ raised:', err)
else:
exit = type(mgr).__exit__
exc = True
try:
try:
BLOCK
except TypeError:
pass
except:
exc = False
try:
exit_val = exit(mgr, *sys.exc_info())
except ValueError as err:
print('__exit__ raised:', err)
else:
if not exit_val:
raise
except ValueError as err:
print('BLOCK raised:', err)
finally:
if exc:
try:
exit(mgr, None, None, None)
except ValueError as err:
print('__exit__ raised:', err)
Habituellement, une approche plus simple fera très bien l'affaire
La nécessité d'une telle gestion des exceptions spéciales devrait être assez rare et normalement, le tout with
dans un try ... except
bloc sera suffisant. Surtout si les diverses sources d'erreur sont indiquées par différents types d'exceptions (personnalisés) (les gestionnaires de contexte doivent être conçus en conséquence), nous pouvons facilement les distinguer. Par exemple:
try:
with ContextManager():
BLOCK
except InitError: # raised from __init__
...
except AcquireResourceError: # raised from __enter__
...
except ValueError: # raised from BLOCK
...
except ReleaseResourceError: # raised from __exit__
...
with
déclaration ne rompt pas comme par magie unetry...except
déclaration environnante .