Attraper une exception lors de l'utilisation d'une instruction Python 'with'


293

À ma honte, je ne peux pas comprendre comment gérer l'exception pour python 'avec' instruction. Si j'ai un code:

with open("a.txt") as f:
    print f.readlines()

Je veux vraiment gérer "l'exception de fichier non trouvé" afin de faire quelque chose. Mais je ne peux pas écrire

with open("a.txt") as f:
    print f.readlines()
except:
    print 'oops'

et ne peut pas écrire

with open("a.txt") as f:
    print f.readlines()
else:
    print 'oops'

enfermer «avec» dans une instruction try / except ne fonctionne pas autrement: l'exception n'est pas déclenchée. Que puis-je faire pour traiter l'échec à l'intérieur de l'instruction 'with' de manière Pythonique?


Que voulez-vous dire par "enfermer 'avec' dans une instruction try / except ne fonctionne pas autrement: l'exception n'est pas levée" ? Une withdéclaration ne rompt pas comme par magie une try...exceptdéclaration environnante .
Aran-Fey

4
Fait intéressant, l'instruction try-with-resources de Java prend en charge exactement le cas d'utilisation que vous souhaitez. docs.oracle.com/javase/tutorial/essential/exceptions/…
Nayuki

Réponses:


256
from __future__ import with_statement

try:
    with open( "a.txt" ) as f :
        print f.readlines()
except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
    print 'oops'

Si vous souhaitez une gestion différente des erreurs de l'appel ouvert par rapport au code de travail, vous pouvez le faire:

try:
    f = open('foo.txt')
except IOError:
    print('error')
else:
    with f:
        print f.readlines()

3
Comme indiqué dans stackoverflow.com/questions/5205811/… , le bloc d'essai ici est vraiment trop large. Aucune distinction n'est faite entre les exceptions lors de la création du gestionnaire de contexte et celles du corps de l'instruction with, ce qui peut ne pas être une solution valide pour tous les cas d'utilisation.
ncoghlan

@ncoghlan Mais vous pouvez ajouter des try...exceptblocs supplémentaires à l'intérieur withpour être plus proche de la source d'une exception qui n'a rien à voir avec open().
rbaleksandar

1
@rbaleksandar Si je me souviens bien, mon commentaire faisait strictement référence au premier exemple de la réponse, où la totalité de l'instruction with se trouve dans le bloc try / except (donc même si vous avez des blocs try / expect intérieurs, toutes les exceptions qu'ils laissent s'échapper seront encore frappé l'extérieur). Douglas a par la suite ajouté le deuxième exemple pour traiter les cas où cette distinction est importante.
ncoghlan

3
Le fichier sera-t-il fermé dans cet exemple? Je demande parce qu'il a été ouvert en dehors de la portée "avec".
Mike Collins

6
@MikeCollins Quitter le 'avec' fermera le fichier ouvert même lorsque le fichier est ouvert avant le 'avec'.
user7938784

75

La meilleure façon "Pythonique" de faire cela, en exploitant l' withinstruction, est répertoriée comme l'exemple # 6 dans PEP 343 , qui donne l'arrière-plan de l'instruction.

@contextmanager
def opened_w_error(filename, mode="r"):
    try:
        f = open(filename, mode)
    except IOError, err:
        yield None, err
    else:
        try:
            yield f, None
        finally:
            f.close()

Utilisé comme suit:

with opened_w_error("/etc/passwd", "a") as (f, err):
    if err:
        print "IOError:", err
    else:
        f.write("guido::0:0::/:/bin/sh\n")

38
Je l'aime mais ça fait un peu trop de magie noire. Ce n'est pas entièrement explicite pour le lecteur
Paul Seeb

5
@PaulSeeb Pourquoi ne le définiriez-vous pas et vous éviter de le faire chaque fois que vous en avez besoin? Il est défini au niveau de votre application, et c'est aussi magique que n'importe quel autre gestionnaire de contexte. Je pense que quelqu'un qui utilise l'instruction with le comprendrait clairement (le nom de la fonction pourrait également être plus expressif si vous ne l'aimez pas). L'instruction "with" elle-même a été conçue pour fonctionner de cette façon, pour définir un bloc de code "sécurisé" et déléguer des fonctions de vérification aux gestionnaires de contexte (pour rendre le code plus clair).

9
Tout ce problème juste pour ne pas avoir écrit le bloc enfin dans le code utilisateur. Je commence à penser que nous souffrons tous d'un long symptôme de battage médiatique sur la déclaration with.
jgomo3

1
La meilleure façon de gérer les exceptions en python est d'écrire une fonction qui les capture et les renvoie? Sérieusement? La manière pythonique de gérer les exceptions consiste à utiliser une try...exceptinstruction.
Aran-Fey

58

Attraper une exception lors de l'utilisation d'une instruction Python 'with'

L'instruction with est disponible sans __future__importation depuis Python 2.6 . Vous pouvez l'obtenir dès Python 2.5 (mais à ce stade, il est temps de mettre à niveau!) Avec:

from __future__ import with_statement

Voici ce que vous avez de plus proche à corriger. Vous y êtes presque, mais withvous n'avez pas de exceptclause:

with open("a.txt") as f: 
    print(f.readlines())
except:                    # <- with doesn't have an except clause.
    print('oops')

La __exit__méthode d'un gestionnaire de contexte , s'il retourne False, relancera l'erreur à la fin. S'il revient True, il le supprimera. L' openintégré __exit__ne revient pas True, il vous suffit donc de l'imbriquer dans un essai, à l'exception du bloc:

try:
    with open("a.txt") as f:
        print(f.readlines())
except Exception as error: 
    print('oops')

Et passe-partout standard: n'utilisez pas un élément except:qui attrape BaseExceptionet toutes les autres exceptions et avertissements possibles. Soyez au moins aussi précis que Exception, et pour cette erreur, peut-être attraper IOError. Ne détectez que les erreurs que vous êtes prêt à gérer.

Donc dans ce cas, vous feriez:

>>> try:
...     with open("a.txt") as f:
...         print(f.readlines())
... except IOError as error: 
...     print('oops')
... 
oops

2

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__
    ...
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.