45 votes

Comment déterminer si une exception a été déclenchée une fois que vous êtes dans le bloc finally?

Est-il possible de savoir si il y a une exception une fois que vous êtes dans l' finally clause? Quelque chose comme:

try:
    funky code
finally:
    if ???:
        print('the funky code raised')

Je cherche à faire quelque chose comme cela, plus SEC:

try:
    funky code
except HandleThis:
    # handle it
    raised = True
except DontHandleThis:
    raised = True
    raise
else:
    raised = False
finally:
    logger.info('funky code raised %s', raised)

Je n'aime pas qu'il a besoin pour attraper une exception, que vous n'avez pas l'intention de manipuler, juste pour définir un indicateur.


Depuis quelques commentaires demandent moins de "M" dans le MCVE, voici un peu plus de fond sur les cas d'utilisation. Le problème réel est d'environ escalade des niveaux de journalisation.

  • Le funky code par un tiers et ne peut pas être changé.
  • L'échec de l'exception et de la trace de la pile ne contient pas des informations de diagnostic utiles, donc à l'aide d' logger.exception dans un bloc except n'est pas utile ici.
  • Si le funky code élevés, puis quelques informations que j'ai besoin de voir a déjà été enregistré, au niveau de DÉBOGAGE. Nous ne sommes pas et ne peut pas gérer l'erreur, mais que vous voulez de dégénérer la journalisation de DÉBOGAGE parce que l'information nécessaire est là.
  • Le funky code ne soulève pas, la plupart du temps. Je ne veux pas aggraver les niveaux de journalisation pour le cas général, car il est trop verbeux.

Par conséquent, le code s'exécute sous un log de capture du contexte (qui met en place des gestionnaires personnalisés pour intercepter les enregistrements de journal) et certaines informations de débogage obtient re-connecté de façon rétrospective:

try:
    with LogCapture() as log:
        funky_code()  # <-- third party badness
finally:
    mylog = mylogger.WARNING if <there was exception> else mylogger.DEBUG
    for record in log.captured:
        mylog(record.msg, record.args)

51voto

MSeifert Points 6307

À l'aide d'un contextmanager

Vous pouvez utiliser un custom contextmanager, par exemple:

class DidWeRaise:
    __slots__ = ('exception_happened', )  # instances will take less memory

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # If no exception happened the `exc_type` is None
        self.exception_happened = exc_type is not None

Et ensuite l'utiliser à l'intérieur de l' try:

try:
    with DidWeRaise() as error_state:
        # funky code
finally:
    if error_state.exception_happened:
        print('the funky code raised')

C'est encore une variable supplémentaire, mais il est sans doute beaucoup plus facile de les réutiliser si vous voulez l'utiliser dans de multiples endroits. Et vous n'avez pas besoin de l'activer vous-même.

À l'aide d'une variable

Dans le cas où vous ne voulez pas le contextmanager je voudrais inverser la logique de la détente et de l'activer uniquement dans le cas où aucune exception qui s'est passé. De cette façon, vous n'avez pas besoin d' except des cas pour les exceptions que vous ne voulez pas gérer. L'endroit le plus approprié serait l' else clause qui est entré dans le cas où l' try n'a pas généré une exception:

exception_happened = True
try:
    # funky code
except HandleThis:
    # handle this kind of exception
else:
    exception_happened = False
finally:
    if exception_happened:
        print('the funky code raised')

Et comme l'a déjà souligné au lieu d'avoir une "bascule" variable vous pouvez le remplacer (dans ce cas) avec la fonction d'enregistrement souhaité:

mylog = mylogger.WARNING
try:
    with LogCapture() as log:
        funky_code()
except HandleThis:
    # handle this kind of exception
else:
    # In case absolutely no exception was thrown in the try we can log on debug level
    mylog = mylogger.DEBUG
finally:
    for record in log.captured:
        mylog(record.msg, record.args)

Bien sûr, il fonctionne également si vous le mettez à la fin de votre try (comme d'autres réponses suggéré ici), mais je préfère l' else clause parce qu'il a plus de sens ("ce code est destiné à être exécuté que si il n'y a aucune exception dans l' try bloc") et peut-être plus facile à maintenir dans le long terme. Bien qu'il soit encore plus à maintenir que le gestionnaire de contexte, parce que la variable est définie et activée dans des endroits différents.

À l'aide de sys.exc_info (fonctionne uniquement pour les exceptions non gérées)

La dernière approche, je tiens à mentionner est probablement pas utile pour vous, mais peut-être utile pour les futurs lecteurs qui veulent seulement savoir si il y a un unhandled exception (une exception est pas pris dans un except bloc ou a été soulevée à l'intérieur d'un except de l'îlot). Dans ce cas, vous pouvez utiliser sys.exc_info:

import sys

try:
    # funky code
except HandleThis:
    pass
finally:
    if sys.exc_info()[0] is not None:
        # only entered if there's an *unhandled* exception, e.g. NOT a HandleThis exception
        print('funky code raised')

31voto

Jean-Paul Calderone Points 27680
 raised = True
try:
    funky code
    raised = False
except HandleThis:
    # handle it
finally:
    logger.info('funky code raised %s', raised)
 

Compte tenu des informations de base supplémentaires ajoutées à la question relative à la sélection d'un niveau de journalisation, cela semble très facilement adapté au cas d'utilisation prévu:

 mylog = WARNING
try:
    funky code
    mylog = DEBUG
except HandleThis:
    # handle it
finally:
    mylog(...)
 

3voto

Jonathan R Points 1426

Vous pouvez facilement affecter votre exception interceptée à une variable et l'utiliser dans le bloc finally, par exemple:

 >>> x = 1
>>> error = None
>>> try:
...     x.foo()
... except Exception as e:
...     error = e
... finally:
...     if error is not None:
...             print(error)
...
'int' object has no attribute 'foo'
 

1voto

Wayne Werner Points 10172

Ok, donc ce que ça sonne comme si vous aviez juste envie de modifier votre gestionnaire de contexte, ou utiliser une approche similaire: logbook a effectivement quelque chose qui s'appelle un FingersCrossedHandler qui ferait exactement ce que vous voulez. Mais vous pourriez le faire vous-même, comme:

@contextmanager
def LogCapture():
    # your existing buffer code here
    level = logging.WARN
    try:
        yield
    except UselessException:
        level = logging.DEBUG
        raise  # Or don't, if you just want it to go away
    finally:
        # emit logs here

Réponse Originale

Vous songez à cela un peu de côté.

Vous ne l'intention de gérer l'exception - vous êtes la manipulation par un indicateur. Peut-être que vous n'avez pas à vous soucier de rien d'autre (ce qui semble être une mauvaise idée), mais si vous vous souciez de faire quelque chose quand une exception est levée, alors vous voulez être explicite à ce sujet.

Le fait que vous êtes à la définition d'une variable, mais vous voulez l'exception de continuer sur signifie que ce que vous vraiment voulez est d'élever votre propre exception de l'exception qui a été levée:

class MyPkgException(Exception):  pass
class MyError(PyPkgException): pass  # If there's another exception type, you can also inherit from that

def do_the_badness():
    try:
        raise FileNotFoundError('Or some other code that raises an error')
    except FileNotFoundError as e:
        raise MyError('File was not found, doh!') from e
    finally:
        do_some_cleanup()

try:
    do_the_badness()
except MyError as e:
    print('The error? Yeah, it happened')

Cela résout:

  • Explicitement de la manipulation de l'exception(s) que vous cherchez à gérer
  • Faire des traces de la pile et de l'origine des exceptions disponible
  • En permettant à votre code qui va gérer l'exception d'origine quelque part d'autre pour gérer vos exception qui est levée
  • En permettant à certains de haut niveau code de gestion des exceptions pour juste attraper MyPkgException pour attraper tous vos exceptions, de sorte qu'elle peut se connecter à quelque chose et à la sortie avec un bon état au lieu d'une vilaine trace de la pile

-1voto

Mike Peder Points 604

Si c'était moi, je referais une petite commande de votre code.

 raised = False
try:
    # funky code
except HandleThis:
    # handle it
    raised = True
except Exception as ex:
    # Don't Handle This 
    raise ex
finally:
    if raised:
        logger.info('funky code was raised')
 

J'ai placé l'affectation booléenne surélevée en dehors de l'instruction try pour en garantir la portée et j'ai transformé l'instruction finale except en un gestionnaire d'exception général pour les exceptions que vous ne souhaitez pas gérer.

Ce style détermine si votre code a échoué. Une autre approche pourrait me permettre de déterminer quand votre code réussira.

 success = False
try:
    # funky code
    success = True
except HandleThis:
    # handle it
    pass
except Exception as ex:
    # Don't Handle This 
    raise ex
finally:
    if success:
        logger.info('funky code was successful')
    else:
        logger.info('funky code was raised')
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X