Différencier les origines possibles des exceptions soulevées à partir d'une withdéclaration composée
La withdistinction 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 withdans un try .. except. Prenons l'exemple suivant (en utilisant ValueErrorcomme 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 exceptcapture 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 withmachine 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 BLOCKet __exit__parce qu'une exception qui échappe au corps du withsera 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 exceptclause générale dans le corps de la withpour stocker toute exception potentielle qui aurait autrement échappé inaperçue et la comparer avec celle capturée le plus à l'extérieur exceptplus tard - s'ils sont les mêmes, cela signifie que l'origine était BLOCKou __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' withinstruction. Ici, nous pouvons facilement envelopper les différentes parties try ... exceptet 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 withdans un try ... exceptbloc 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__
...
withdéclaration ne rompt pas comme par magie unetry...exceptdéclaration environnante .