Levée (levée) manuelle d'une exception en Python

2266

Comment puis-je déclencher une exception en Python afin qu'elle puisse plus tard être interceptée via un exceptbloc?

TIMEX
la source

Réponses:

2939

Comment lancer / lever manuellement une exception en Python?

Utilisez le constructeur d'exceptions le plus spécifique qui correspond sémantiquement à votre problème .

Soyez précis dans votre message, par exemple:

raise ValueError('A very specific bad thing happened.')

Ne pas lever d'exceptions génériques

Évitez d'élever un générique Exception. Pour l'attraper, vous devrez attraper toutes les autres exceptions plus spécifiques qui le sous-classent.

Problème 1: masquage des bogues

raise Exception('I know Python!') # Don't! If you catch, likely to hide bugs.

Par exemple:

def demo_bad_catch():
    try:
        raise ValueError('Represents a hidden bug, do not catch this')
        raise Exception('This is the exception you expect to handle')
    except Exception as error:
        print('Caught this error: ' + repr(error))

>>> demo_bad_catch()
Caught this error: ValueError('Represents a hidden bug, do not catch this',)

Problème 2: n'attrapera pas

Et les captures plus spécifiques n'attraperont pas l'exception générale:

def demo_no_catch():
    try:
        raise Exception('general exceptions not caught by specific handling')
    except ValueError as e:
        print('we will not catch exception: Exception')


>>> demo_no_catch()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in demo_no_catch
Exception: general exceptions not caught by specific handling

Meilleures pratiques: raisedéclaration

Utilisez plutôt le constructeur d'exceptions le plus spécifique qui correspond sémantiquement à votre problème .

raise ValueError('A very specific bad thing happened')

ce qui permet également de transmettre un nombre arbitraire d'arguments au constructeur:

raise ValueError('A very specific bad thing happened', 'foo', 'bar', 'baz') 

Ces arguments sont accessibles par l' argsattribut sur l' Exceptionobjet. Par exemple:

try:
    some_code_that_may_raise_our_value_error()
except ValueError as err:
    print(err.args)

impressions

('message', 'foo', 'bar', 'baz')    

Dans Python 2.5, un messageattribut réel a été ajouté pour BaseExceptionencourager les utilisateurs à sous-classer les exceptions et à cesser d'utiliser args, mais l'introduction messageet la dépréciation d'origine des arguments ont été retirées .

Meilleures pratiques: exceptclause

Lorsque vous êtes dans une clause except, vous souhaiterez peut-être, par exemple, enregistrer qu'un type d'erreur spécifique s'est produit, puis sur-relancer. La meilleure façon de le faire tout en préservant la trace de pile est d'utiliser une instruction de relance nue. Par exemple:

logger = logging.getLogger(__name__)

try:
    do_something_in_app_that_breaks_easily()
except AppError as error:
    logger.error(error)
    raise                 # just this!
    # raise AppError      # Don't do this, you'll lose the stack trace!

Ne modifiez pas vos erreurs ... mais si vous insistez.

Vous pouvez conserver le stacktrace (et la valeur d'erreur) avec sys.exc_info(), mais c'est beaucoup plus sujet aux erreurs et a des problèmes de compatibilité entre Python 2 et 3 , préférez utiliser un bare raisepour relancer.

Pour expliquer - le sys.exc_info()retourne le type, la valeur et traceback.

type, value, traceback = sys.exc_info()

C'est la syntaxe de Python 2 - notez que ce n'est pas compatible avec Python 3:

    raise AppError, error, sys.exc_info()[2] # avoid this.
    # Equivalently, as error *is* the second object:
    raise sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2]

Si vous le souhaitez, vous pouvez modifier ce qui se passe avec votre nouvelle augmentation - par exemple, définir un nouveau argspour l'instance:

def error():
    raise ValueError('oops!')

def catch_error_modify_message():
    try:
        error()
    except ValueError:
        error_type, error_instance, traceback = sys.exc_info()
        error_instance.args = (error_instance.args[0] + ' <modification>',)
        raise error_type, error_instance, traceback

Et nous avons conservé l'intégralité de la trace en modifiant les arguments. Notez que ce n'est pas une meilleure pratique et que c'est une syntaxe invalide dans Python 3 (ce qui rend la compatibilité beaucoup plus difficile à contourner).

>>> catch_error_modify_message()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in catch_error_modify_message
  File "<stdin>", line 2, in error
ValueError: oops! <modification>

En Python 3 :

    raise error.with_traceback(sys.exc_info()[2])

Encore une fois: évitez de manipuler manuellement les retraits. C'est moins efficace et plus sujet aux erreurs. Et si vous utilisez le filetage et que sys.exc_infovous pouvez même obtenir le mauvais retraçage (surtout si vous utilisez la gestion des exceptions pour le flux de contrôle - ce que j'aurais tendance à éviter personnellement.)

Python 3, chaînage d'exception

En Python 3, vous pouvez chaîner des exceptions, qui préservent les retraits:

    raise RuntimeError('specific message') from error

Être conscient:

  • cela ne permet de changer le type d'erreur élevé, et
  • ce n'est pas compatible avec Python 2.

Méthodes obsolètes:

Ceux-ci peuvent facilement se cacher et même entrer dans le code de production. Vous voulez lever une exception, et les faire lèvera une exception, mais pas celle prévue!

Valide en Python 2, mais pas en Python 3 est le suivant:

raise ValueError, 'message' # Don't do this, it's deprecated!

Uniquement valable dans les versions beaucoup plus anciennes de Python (2.4 et inférieures), vous pouvez toujours voir des gens lever des chaînes:

raise 'message' # really really wrong. don't do this.

Dans toutes les versions modernes, cela soulèvera en fait un TypeError, car vous n'élevez pas un BaseExceptiontype. Si vous ne recherchez pas la bonne exception et que vous n'avez pas de réviseur au courant du problème, il pourrait entrer en production.

Exemple d'utilisation

Je lève des exceptions pour avertir les consommateurs de mon API s'ils ne l'utilisent pas correctement:

def api_func(foo):
    '''foo should be either 'baz' or 'bar'. returns something very useful.'''
    if foo not in _ALLOWED_ARGS:
        raise ValueError('{foo} wrong, use "baz" or "bar"'.format(foo=repr(foo)))

Créez vos propres types d'erreur lorsque

"Je veux faire une erreur exprès, pour que ça aille dans l'exception"

Vous pouvez créer vos propres types d'erreur, si vous souhaitez indiquer que quelque chose de spécifique ne va pas avec votre application, il suffit de sous-classer le point approprié dans la hiérarchie des exceptions:

class MyAppLookupError(LookupError):
    '''raise this when there's a lookup error for my app'''

et utilisation:

if important_key not in resource_dict and not ok_to_be_missing:
    raise MyAppLookupError('resource is missing, and that is not ok.')
Aaron Hall
la source
19
Merci pour cela, c'est exactement ce dont j'avais besoin. Le bare raiseest ce dont j'avais besoin pour pouvoir effectuer un débogage d'erreur personnalisé à plusieurs niveaux d'exécution de code sans interrompre la trace de la pile.
CaffeineConnoisseur
C'est une excellente réponse. Mais je travaille toujours avec beaucoup de code 2.7, et je me retrouve souvent à vouloir ajouter des informations à une exception inattendue, comme une position de fichier d'entrée ou les valeurs de certaines variables, mais garder la pile et l'exception d'origine. Je peux le journaliser, mais parfois je ne le veux pas, par exemple si le code parent le gère finalement. raise sys.exc_info()[0], (sys.exc_info()[1], my_extra_info), sys.exc_info()[2]semble faire ce que je veux, et je n'ai jamais rencontré de problèmes avec ça. Mais cela semble hacky et pas une pratique acceptée. Y a-t-il une meilleure façon?
Michael Scheper
2
@brennanyoung Dans ce contexte, je pense qu'il peut être déroutant de déclencher une SyntaxError - vous devriez probablement lever une exception personnalisée. J'explique comment faire ici: stackoverflow.com/a/26938914/541136
Aaron Hall
2
Notez que la citation complète est "Toutes les exceptions intégrées, qui ne sortent pas du système, sont dérivées de cette classe. Toutes les exceptions définies par l'utilisateur doivent également être dérivées de cette classe." - Cela signifie principalement que vous ne devez pas utiliser l'une des 4 exceptions qui ne dérivent pas de Exceptionvotre classe parent - vous pouvez sous-classer quelque chose de plus spécifique, et devriez le faire si cela a du sens.
Aaron Hall
1
Dans l'exemple de " Best Practices: except clause ", vous utilisez une AppErrorexception non définie . Il peut être préférable d'utiliser une erreur AttributeError
intégrée
530

NE FAITES PAS CELA . Élever une nue Exceptionn'est absolument pas la bonne chose à faire; voir plutôt l'excellente réponse d'Aaron Hall .

Impossible d'obtenir beaucoup plus de pythonic que cela:

raise Exception("I know python!")

Voir les documents d'instructions raise pour python si vous souhaitez plus d'informations.

Gabriel Hurley
la source
67
Non je t'en prie! Cela supprime le potentiel d'être précis sur ce que vous attrapez. C'est ENTIÈREMENT la mauvaise façon de procéder. Jetez un œil à l'excellente réponse d'Aaron Hall au lieu de celle-ci. C'est dans des moments comme celui-ci que j'aimerais pouvoir donner plus d'un downvote par réponse.
Dawood ibn Kareem
27
@PeterR C'est tout aussi terrible qu'il y ait si peu de downvotes. À QUI QUE CE SOIT la lecture de cette réponse, NE FAITES JAMAIS CECI! La bonne réponse est celle d'Aaron Hall.
Dawood ibn Kareem
6
Je pense qu'il devrait y avoir une explication plus détaillée des raisons pour lesquelles cela est mauvais ou si mauvais.
Charlie Parker
9
@CharlieParker Il y en a. C'est la première partie de la réponse d' Aaron Hall .
Dinei
5
Pourquoi cette réponse ne peut-elle pas être signalée pour suppression? Il a déjà 93 downvotes!
codeforester
55

En Python3, il existe 4 syntaxes différentes pour éliminer les exceptions:

1. raise exception 
2. raise exception (args) 
3. raise
4. raise exception (args) from original_exception

1. lever l'exception vs 2. lever l'exception (args)

Si vous utilisez raise exception (args) pour déclencher une exception, le argssera imprimé lorsque vous imprimerez l'objet d'exception - comme illustré dans l'exemple ci-dessous.

  #raise exception (args)
    try:
        raise ValueError("I have raised an Exception")
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error I have raised an Exception 



  #raise execption 
    try:
        raise ValueError
    except ValueError as exp:
        print ("Error", exp)     # Output -> Error 

3. élever

raisesans aucun argument relance la dernière exception. Ceci est utile si vous devez effectuer certaines actions après avoir intercepté l'exception et que vous souhaitez ensuite la relancer. Mais s'il n'y avait pas d'exception auparavant, l' raiseinstruction déclenche TypeErrorException.

def somefunction():
    print("some cleaning")

a=10
b=0 
result=None

try:
    result=a/b
    print(result)

except Exception:            #Output ->
    somefunction()           #some cleaning
    raise                    #Traceback (most recent call last):
                             #File "python", line 8, in <module>
                             #ZeroDivisionError: division by zero

4. lever l'exception (args) de original_exception

Cette instruction est utilisée pour créer un chaînage d'exceptions dans lequel une exception déclenchée en réponse à une autre exception peut contenir les détails de l'exception d'origine, comme illustré dans l'exemple ci-dessous.

class MyCustomException(Exception):
pass

a=10
b=0 
reuslt=None
try:
    try:
        result=a/b

    except ZeroDivisionError as exp:
        print("ZeroDivisionError -- ",exp)
        raise MyCustomException("Zero Division ") from exp

except MyCustomException as exp:
        print("MyException",exp)
        print(exp.__cause__)

Production:

ZeroDivisionError --  division by zero
MyException Zero Division 
division by zero
N Randhawa
la source
7
S'il vous plaît noter que pep8 préfère exception(args)plusexception (args)
Gloweye
Il faut également raise exception(args) from Nonedire que l'exception actuellement active a été gérée et ne présente plus d'intérêt. Sinon, si vous déclenchez une exception dans un exceptbloc et qu'elle n'est pas gérée, les retraits pour les deux exceptions seront affichés séparés par le message «Pendant la gestion de l'exception ci-dessus, une autre exception s'est produite»
cg909
35

Pour le cas courant où vous devez lever une exception en réponse à des conditions inattendues et que vous n'avez jamais l'intention d'attraper, mais simplement d'échouer rapidement pour vous permettre de déboguer à partir de là si cela se produit - la plus logique semble être AssertionError:

if 0 < distance <= RADIUS:
    #Do something.
elif RADIUS < distance:
    #Do something.
else:
    raise AssertionError("Unexpected value of 'distance'!", distance)
Evgeni Sergeev
la source
19
C'est un meilleur cas ValueErrorque AssertionErrorparce qu'il n'y a pas de problème avec une assertion (car aucun n'est fait ici) - le problème est avec une valeur. Si vous voulez vraiment un AssertionErrordans ce cas, écrivez assert distance > 0, 'Distance must be positive'. Mais vous ne devriez pas vérifier les erreurs de cette façon car les assertions peuvent être désactivées ( python -O).
Two-Bit Alchemist
1
@ Two-BitAlchemist Bon point. L'idée a été perdue dans la simplification, quand j'ai écrit l'exemple simple ci-dessus. Dans de nombreux cas similaires, il s'agit d'une condition qui n'est pas associée à une valeur particulière. Au contraire, le sens est "le flux de contrôle ne devrait jamais arriver ici".
Evgeni Sergeev
2
@ Les assertions Two-BitAlchemist peuvent être désactivées, oui, mais vous ne devriez pas du tout les utiliser pour vérifier les erreurs?
Evgeni Sergeev
En fait ça dépend. Je ne laisserais pas cela être ma seule erreur lors de la vérification d'un programme que j'avais l'intention de distribuer. D'un autre côté, je pourrais créer un programme juste pour mes collègues et leur dire qu'ils l'utilisent à leurs risques et périls s'ils l'exécutent avec -O.
Two-Bit Alchemist
1
@ Two-BitAlchemist Pour moi, le rôle des assertions n'est pas la vérification des erreurs en soi (ce à quoi servent les tests), mais ils mettent en place des clôtures dans le code que certains bogues ne peuvent pas franchir. Il devient donc plus facile de localiser et d'isoler les bogues, ce qui se produira inévitablement. Ce ne sont que de bonnes habitudes qui demandent peu d'efforts, tandis que les tests demandent beaucoup d'efforts et beaucoup de temps.
Evgeni Sergeev
12

Lisez d'abord les réponses existantes, ce n'est qu'un addendum.

Notez que vous pouvez déclencher des exceptions avec ou sans arguments.

Exemple:

raise SystemExit

quitte le programme mais vous voudrez peut-être savoir ce qui s'est passé. Vous pouvez donc l'utiliser.

raise SystemExit("program exited")

cela affichera "programme quitté" sur stderr avant de fermer le programme.

Anant Prakash
la source
2
N'est-ce pas contraire au paradigme OOP? Je suppose que le premier cas renvoie la référence de classe et le second une instance de SystemExit. Ce ne serait pas raise SystemExit()le meilleur choix? Pourquoi le premier fonctionne-t-il même?
burny
2

Une autre façon de lever une exception est assert. Vous pouvez utiliser assert pour vérifier qu'une condition est remplie sinon, elle augmentera AssertionError. Pour plus de détails jetez un œil ici .

def avg(marks):
    assert len(marks) != 0,"List is empty."
    return sum(marks)/len(marks)

mark2 = [55,88,78,90,79]
print("Average of mark2:",avg(mark2))

mark1 = []
print("Average of mark1:",avg(mark1))
Rehan Haider
la source
2

Juste pour noter: il y a des moments où vous voulez gérer les exceptions génériques. Si vous traitez un tas de fichiers et enregistrez vos erreurs, vous souhaiterez peut-être intercepter toute erreur qui se produit pour un fichier, l'enregistrer et continuer à traiter le reste des fichiers. Dans ce cas, un

try:
    foo() 
except Exception as e:
    print(str(e)) # Print out handled error

bloquer une bonne façon de le faire. Vous voudrez toujours raisedes exceptions spécifiques afin que vous sachiez ce qu'elles signifient, cependant.

Markemus
la source
0

Vous devriez apprendre l'instruction raise de python pour cela. Il doit être conservé à l'intérieur du bloc try. Exemple -

try:
    raise TypeError            #remove TypeError by any other error if you want
except TypeError:
    print('TypeError raised')
Abhijeet.py
la source