web-dev-qa-db-fra.com

Interception d'une exception lors de l'utilisation d'une instruction Python 'avec'

À ma grande honte, je ne vois pas comment gérer une exception pour python 'avec'. Si j'ai un code:

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

Je veux vraiment gérer "fichier non trouvé exception" 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'

inclure '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 d'une déclaration 'with' de manière pythonique?

250
grigoryvp
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 effectuer les opérations suivantes:

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

Le meilleur moyen "pythonique" de le faire, exploitant l'instruction with, est répertorié comme exemple n ° 6 dans PEP 34 , 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")
63
user765144

Interception d'une exception lors de l'utilisation d'une instruction Python 'avec'

L'instruction with est disponible sans le __future__ import depuis Python 2.6 . Vous pouvez l’obtenir sous forme de dès le début de Python 2.5 (mais il est maintenant temps de mettre à niveau!) Avec:

from __future__ import with_statement

Voici la chose la plus proche à corriger que vous avez. Vous y êtes presque, mais with n'a pas de clause except:

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

La méthode __exit__ d'un gestionnaire de contexte, si elle renvoie False, relèvera l'erreur à la fin. S'il retourne True, il le supprimera. Le __exit__ intégré de open ne retourne pas True, vous devez donc simplement l'imbriquer dans un essai, sauf le bloc:

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

Et le standard standard: ne pas utiliser un except: nu qui capture BaseException et toutes les autres exceptions et avertissements possibles. Soyez au moins aussi spécifique que Exception, et pour cette erreur, attraperez peut-être IOError. Attrapez uniquement 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
51
Aaron Hall

Différencier les origines possibles des exceptions issues d'une instruction composée with

Différencier les exceptions qui se produisent dans une instruction with est délicat, car elles peuvent être originaires de différents endroits. Des exceptions peuvent être levées à partir de l'un des emplacements suivants (ou fonctions appelées à cet endroit):

  • ContextManager.__init__
  • ContextManager.__enter__
  • le corps du with
  • ContextManager.__exit__

Pour plus de détails, voir la documentation sur types de gestionnaire de contexte .

Si nous voulons faire la distinction entre ces différents cas, il n’est pas suffisant d’envelopper le with dans un try .. except. Prenons l'exemple suivant (en utilisant ValueError comme exemple, mais il pourrait bien sûr être remplacé par tout autre type d'exception):

try:
    with ContextManager():
        BLOCK
except ValueError as err:
    print(err)

Ici, la except interceptera les exceptions provenant des quatre endroits différents et ne permet donc pas de les distinguer. Si nous déplaçons l'instanciation de l'objet de gestionnaire de contexte en dehors de with, nous pouvons faire la distinction 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

Effectivement, cela n’a aidé que la partie __init__, mais nous pouvons ajouter une variable sentinelle supplémentaire pour vérifier si le corps de la with a commencé à s’exécuter (c.-à-d. Une différenciation entre __enter__ et 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 difficulté consiste à faire la différence entre les exceptions provenant de BLOCK et __exit__, car une exception échappant au corps de la with sera transmise à __exit__, qui peut décider de la gestion. le (voir la documentation ). Si toutefois __exit__ se soulève, l'exception originale sera remplacée par la nouvelle. Pour traiter ces cas, nous pouvons ajouter une clause générale except dans le corps de la with pour stocker toute exception potentielle qui aurait autrement échappé à la connaissance et la comparer à celle prise dans la partie la plus externe except plus tard - si elles sont identiques, cela signifie que l’origine était BLOCK ou sinon __exit__ (dans le cas où __exit__ supprime l’exception en renvoyant une valeur vraie la plus extérieure except _ 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 la forme équivalente mentionnée dans PEP 343

PEP 343 - L'instruction "with" spécifie une version "non-with" équivalente de l'instruction with. Ici, nous pouvons facilement envelopper les différentes parties avec 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 il suffira d'envelopper tout le with dans un bloc try ... except. Surtout si les différentes 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__
    ...
1
a_guest