Est-ce une bonne pratique d'utiliser try-except-else en Python?

440

De temps en temps en Python, je vois le bloc:

try:
   try_this(whatever)
except SomeException as exception:
   #Handle exception
else:
   return something

Quelle est la raison pour laquelle le try-except-else existe?

Je n'aime pas ce genre de programmation, car elle utilise des exceptions pour effectuer le contrôle de flux. Cependant, s'il est inclus dans la langue, il doit y avoir une bonne raison, n'est-ce pas?

Je crois comprendre que les exceptions ne sont pas des erreurs et qu'elles ne doivent être utilisées que dans des conditions exceptionnelles (par exemple, j'essaie d'écrire un fichier sur le disque et il n'y a plus d'espace, ou peut-être que je n'ai pas l'autorisation), et pas pour le flux contrôle.

Normalement, je gère les exceptions comme:

something = some_default_value
try:
    something = try_this(whatever)
except SomeException as exception:
    #Handle exception
finally:
    return something

Ou si je ne veux vraiment rien retourner si une exception se produit, alors:

try:
    something = try_this(whatever)
    return something
except SomeException as exception:
    #Handle exception
Juan Antonio Gomez Moriano
la source

Réponses:

668

"Je ne sais pas si c'est par ignorance, mais je n'aime pas ce genre de programmation, car il utilise des exceptions pour effectuer le contrôle de flux."

Dans le monde Python, l'utilisation d'exceptions pour le contrôle de flux est courante et normale.

Même les développeurs principaux de Python utilisent des exceptions pour le contrôle de flux et ce style est fortement intégré dans le langage (c'est-à-dire que le protocole itérateur utilise StopIteration pour signaler la fin de la boucle).

En outre, le style try-except-est utilisé pour empêcher les conditions de concurrence inhérentes à certaines des constructions "à regarder avant de sauter" . Par exemple, le test de os.path.exists entraîne des informations qui peuvent être obsolètes au moment où vous les utilisez. De même, Queue.full renvoie des informations qui peuvent être périmées. Le style try-except-else produira du code plus fiable dans ces cas.

"Je crois comprendre que les exceptions ne sont pas des erreurs, elles ne doivent être utilisées que dans des conditions exceptionnelles"

Dans certaines autres langues, cette règle reflète leurs normes culturelles telles que reflétées dans leurs bibliothèques. La "règle" est également basée en partie sur des considérations de performances pour ces langues.

La norme culturelle Python est quelque peu différente. Dans de nombreux cas, vous devez utiliser des exceptions pour le flux de contrôle. En outre, l'utilisation d'exceptions en Python ne ralentit pas le code environnant et le code appelant comme il le fait dans certains langages compilés (c'est-à-dire que CPython implémente déjà du code pour la vérification des exceptions à chaque étape, que vous utilisiez ou non des exceptions).

En d'autres termes, votre compréhension que "les exceptions sont pour l'exceptionnel" est une règle qui a du sens dans certains autres langages, mais pas pour Python.

"Cependant, s'il est inclus dans la langue elle-même, il doit y avoir une bonne raison, n'est-ce pas?"

En plus d'aider à éviter les conditions de concurrence, les exceptions sont également très utiles pour tirer des boucles extérieures de gestion des erreurs. Il s'agit d'une optimisation nécessaire dans les langages interprétés qui n'ont pas tendance à avoir un mouvement de code invariant de boucle automatique .

En outre, les exceptions peuvent simplifier un peu le code dans des situations courantes où la possibilité de gérer un problème est très éloignée de l'origine du problème. Par exemple, il est courant d'avoir un code d'appel de code d'interface utilisateur de niveau supérieur pour la logique métier qui à son tour appelle des routines de bas niveau. Les situations survenant dans les routines de bas niveau (telles que les enregistrements en double pour des clés uniques dans les accès à la base de données) ne peuvent être gérées que dans le code de niveau supérieur (comme demander à l'utilisateur une nouvelle clé qui n'entre pas en conflit avec les clés existantes). L'utilisation d'exceptions pour ce type de flux de contrôle permet aux routines de niveau intermédiaire d'ignorer complètement le problème et d'être bien dissociées de cet aspect du contrôle de flux.

Il y a un joli billet de blog sur le caractère indispensable des exceptions ici .

Voir également cette réponse Stack Overflow: les exceptions sont-elles vraiment des erreurs exceptionnelles?

"Quelle est la raison pour laquelle le try-except-else existe?"

La clause else elle-même est intéressante. Il s'exécute lorsqu'il n'y a pas d'exception mais avant la clause finally. Tel est son objectif principal.

Sans la clause else, la seule option pour exécuter du code supplémentaire avant la finalisation serait la pratique maladroite d'ajouter le code à la clause try. C'est maladroit car cela risque de soulever des exceptions dans du code qui n'était pas destiné à être protégé par le bloc d'essai.

Le cas d'utilisation de l'exécution de code supplémentaire non protégé avant la finalisation ne se pose pas très souvent. Donc, ne vous attendez pas à voir de nombreux exemples dans le code publié. C'est assez rare.

Un autre cas d'utilisation de la clause else consiste à effectuer des actions qui doivent se produire lorsqu'aucune exception ne se produit et qui ne se produisent pas lorsque des exceptions sont gérées. Par exemple:

recip = float('Inf')
try:
    recip = 1 / f(x)
except ZeroDivisionError:
    logging.info('Infinite result')
else:
    logging.info('Finite result')

Un autre exemple se produit chez les coureurs les moins performants:

try:
    tests_run += 1
    run_testcase(case)
except Exception:
    tests_failed += 1
    logging.exception('Failing test case: %r', case)
    print('F', end='')
else:
    logging.info('Successful test case: %r', case)
    print('.', end='')

Enfin, l'utilisation la plus courante d'une clause else dans un bloc d'essai est pour un peu d'embellissement (en alignant les résultats exceptionnels et les résultats non exceptionnels au même niveau d'indentation). Cette utilisation est toujours facultative et n'est pas strictement nécessaire.

Raymond Hettinger
la source
29
"C'est maladroit car cela risque de soulever des exceptions dans le code qui n'était pas destiné à être protégé par le bloc d'essai." C'est l'apprentissage le plus important ici
Felix Dombek
2
Merci d'avoir répondu. Pour les lecteurs à la recherche d'exemples d'utilisation try-except-else, jetez un œil à la méthode copyfile
suripoori
2
Le fait est que la clause else n'est exécutée que lorsque la clause try réussit.
Jonathan
173

Quelle est la raison pour laquelle le try-except-else existe?

Un trybloc vous permet de gérer une erreur attendue. Le exceptbloc ne doit intercepter que les exceptions que vous êtes prêt à gérer. Si vous gérez une erreur inattendue, votre code peut faire la mauvaise chose et masquer les bogues.

Une elseclause s'exécutera s'il n'y a pas eu d'erreurs, et en n'exécutant pas ce code dans le trybloc, vous éviterez d'attraper une erreur inattendue. Encore une fois, la capture d'une erreur inattendue peut masquer les bogues.

Exemple

Par exemple:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
else:
    return something

La suite "try, except" a deux clauses facultatives, elseet finally. C'est donc en fait try-except-else-finally.

elsen'évaluera que s'il n'y a pas d'exception du trybloc. Cela nous permet de simplifier le code plus compliqué ci-dessous:

no_error = None
try:
    try_this(whatever)
    no_error = True
except SomeException as the_exception:
    handle(the_exception)
if no_error:
    return something

donc si nous comparons un elseà l'alternative (qui pourrait créer des bogues), nous voyons qu'il réduit les lignes de code et nous pouvons avoir une base de code plus lisible, plus facile à entretenir et moins boguée.

finally

finally s'exécutera quoi qu'il arrive, même si une autre ligne est évaluée avec une instruction de retour.

Décomposé avec un pseudo-code

Cela pourrait aider à décomposer cela, dans la forme la plus petite possible qui montre toutes les fonctionnalités, avec des commentaires. Supposons que ce pseudo-code syntaxiquement correct (mais non exécutable sauf si les noms sont définis) se trouve dans une fonction.

Par exemple:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle_SomeException(the_exception)
    # Handle a instance of SomeException or a subclass of it.
except Exception as the_exception:
    generic_handle(the_exception)
    # Handle any other exception that inherits from Exception
    # - doesn't include GeneratorExit, KeyboardInterrupt, SystemExit
    # Avoid bare `except:`
else: # there was no exception whatsoever
    return something()
    # if no exception, the "something()" gets evaluated,
    # but the return will not be executed due to the return in the
    # finally block below.
finally:
    # this block will execute no matter what, even if no exception,
    # after "something" is eval'd but before that value is returned
    # but even if there is an exception.
    # a return here will hijack the return functionality. e.g.:
    return True # hijacks the return in the else clause above

Il est vrai que nous pourrions inclure le code dans le elsebloc dans le trybloc à la place, où il s'exécuterait s'il n'y avait pas d'exceptions, mais que faire si ce code lui-même déclenche une exception du type que nous interceptons? Le laisser dans le trybloc cacherait ce bug.

Nous voulons minimiser les lignes de code dans le trybloc pour éviter d'attraper des exceptions auxquelles nous ne nous attendions pas, en vertu du principe que si notre code échoue, nous voulons qu'il échoue bruyamment. Il s'agit d'une meilleure pratique .

Je crois comprendre que les exceptions ne sont pas des erreurs

En Python, la plupart des exceptions sont des erreurs.

Nous pouvons afficher la hiérarchie des exceptions en utilisant pydoc. Par exemple, en Python 2:

$ python -m pydoc exceptions

ou Python 3:

$ python -m pydoc builtins

Nous donnera la hiérarchie. Nous pouvons voir que la plupart des types d' Exceptionerreurs sont des erreurs, bien que Python en utilise certaines pour des choses comme la fin des forboucles ( StopIteration). Voici la hiérarchie de Python 3:

BaseException
    Exception
        ArithmeticError
            FloatingPointError
            OverflowError
            ZeroDivisionError
        AssertionError
        AttributeError
        BufferError
        EOFError
        ImportError
            ModuleNotFoundError
        LookupError
            IndexError
            KeyError
        MemoryError
        NameError
            UnboundLocalError
        OSError
            BlockingIOError
            ChildProcessError
            ConnectionError
                BrokenPipeError
                ConnectionAbortedError
                ConnectionRefusedError
                ConnectionResetError
            FileExistsError
            FileNotFoundError
            InterruptedError
            IsADirectoryError
            NotADirectoryError
            PermissionError
            ProcessLookupError
            TimeoutError
        ReferenceError
        RuntimeError
            NotImplementedError
            RecursionError
        StopAsyncIteration
        StopIteration
        SyntaxError
            IndentationError
                TabError
        SystemError
        TypeError
        ValueError
            UnicodeError
                UnicodeDecodeError
                UnicodeEncodeError
                UnicodeTranslateError
        Warning
            BytesWarning
            DeprecationWarning
            FutureWarning
            ImportWarning
            PendingDeprecationWarning
            ResourceWarning
            RuntimeWarning
            SyntaxWarning
            UnicodeWarning
            UserWarning
    GeneratorExit
    KeyboardInterrupt
    SystemExit

Un intervenant a demandé:

Supposons que vous ayez une méthode qui envoie une requête ping à une API externe et que vous souhaitiez gérer l'exception dans une classe en dehors de l'encapsuleur d'API, renvoyez-vous simplement e de la méthode sous la clause except où e est l'objet d'exception?

Non, vous ne renvoyez pas l'exception, relancez-la simplement avec un nu raisepour conserver la trace de pile.

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise

Ou, dans Python 3, vous pouvez déclencher une nouvelle exception et conserver la trace avec chaînage d'exceptions:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise DifferentException from the_exception

J'élabore dans ma réponse ici .

Aaron Hall
la source
voté! que faites-vous habituellement à l'intérieur de la poignée? disons que vous avez une méthode qui envoie une requête ping à une API externe et que vous souhaitez gérer l'exception à une classe en dehors de l'encapsuleur API, renvoyez-vous simplement e à partir de la méthode sous la clause except où e est l'objet d'exception?
PirateApp
1
@PirateApp merci! non, ne le retournez pas, vous devriez probablement relancer avec un raisechaînage d'exception nu ou faire - mais c'est plus sur le sujet et couvert ici: stackoverflow.com/q/2052390/541136 - je supprimerai probablement ces commentaires après avoir vu que vous les avez vus.
Aaron Hall
merci beaucoup pour les détails! en passant par le poste maintenant
PirateApp
36

Python ne souscrit pas à l'idée que les exceptions ne devraient être utilisées que dans des cas exceptionnels, en fait l'idiome est «demander pardon, pas permission» . Cela signifie que l'utilisation d'exceptions en tant que partie de routine de votre contrôle de flux est parfaitement acceptable et, en fait, encouragée.

C'est généralement une bonne chose, car travailler de cette façon permet d'éviter certains problèmes (comme exemple évident, les conditions de concurrence sont souvent évitées), et cela tend à rendre le code un peu plus lisible.

Imaginez que vous ayez une situation où vous prenez une entrée utilisateur qui doit être traitée, mais avez une valeur par défaut qui est déjà traitée. La try: ... except: ... else: ...structure rend le code très lisible:

try:
   raw_value = int(input())
except ValueError:
   value = some_processed_value
else: # no error occured
   value = process_value(raw_value)

Comparez avec la façon dont cela pourrait fonctionner dans d'autres langues:

raw_value = input()
if valid_number(raw_value):
    value = process_value(int(raw_value))
else:
    value = some_processed_value

Notez les avantages. Il n'est pas nécessaire de vérifier que la valeur est valide et de l'analyser séparément, elles sont effectuées une fois. Le code suit également une progression plus logique, le chemin du code principal est le premier, suivi de «si cela ne fonctionne pas, faites-le».

L'exemple est naturellement un peu artificiel, mais il montre qu'il y a des cas pour cette structure.

Gareth Latty
la source
15

Est-ce une bonne pratique d'utiliser try-except-else en python?

La réponse à cela est qu'elle dépend du contexte. Si tu fais ça:

d = dict()
try:
    item = d['item']
except KeyError:
    item = 'default'

Cela montre que vous ne connaissez pas très bien Python. Cette fonctionnalité est encapsulée dans la dict.getméthode:

item = d.get('item', 'default')

Le try/ exceptbloc est une façon beaucoup plus visuellement encombrée et verbeuse d'écrire ce qui peut être exécuté efficacement sur une seule ligne avec une méthode atomique. Il y a d'autres cas où cela est vrai.

Cependant, cela ne signifie pas que nous devons éviter toute gestion des exceptions. Dans certains cas, il est préférable d'éviter les conditions de course. Ne vérifiez pas si un fichier existe, essayez simplement de l'ouvrir et interceptez l'erreur IOError appropriée. Dans un souci de simplicité et de lisibilité, essayez de l'encapsuler ou de le factoriser comme il convient.

Lisez le Zen de Python , en comprenant qu'il y a des principes qui sont en tension, et méfiez-vous des dogmes qui reposent trop fortement sur l'une des déclarations qu'il contient.

Aaron Hall
la source
12

Voir l'exemple suivant qui illustre tout sur try-except-else-finally:

for i in range(3):
    try:
        y = 1 / i
    except ZeroDivisionError:
        print(f"\ti = {i}")
        print("\tError report: ZeroDivisionError")
    else:
        print(f"\ti = {i}")
        print(f"\tNo error report and y equals {y}")
    finally:
        print("Try block is run.")

Implémentez-le et venez par:

    i = 0
    Error report: ZeroDivisionError
Try block is run.
    i = 1
    No error report and y equals 1.0
Try block is run.
    i = 2
    No error report and y equals 0.5
Try block is run.
Calcul
la source
3
Il s'agit d'un excellent exemple simple qui illustre rapidement la clause try complète sans exiger que quelqu'un (qui peut être très pressé) lise une longue explication abstraite. (Bien sûr, quand ils ne sont plus pressés, ils devraient revenir et lire le résumé complet.)
GlobalSoftwareSociety
6

Vous devez faire attention à utiliser le bloc finally, car ce n'est pas la même chose que d'utiliser un bloc else dans l'essai, sauf. Le bloc finalement sera exécuté indépendamment du résultat de l'essai sauf.

In [10]: dict_ = {"a": 1}

In [11]: try:
   ....:     dict_["b"]
   ....: except KeyError:
   ....:     pass
   ....: finally:
   ....:     print "something"
   ....:     
something

Comme tout le monde l'a noté, l'utilisation du bloc else rend votre code plus lisible et ne s'exécute que lorsqu'une exception n'est pas levée

In [14]: try:
             dict_["b"]
         except KeyError:
             pass
         else:
             print "something"
   ....:
Greg
la source
Je sais que finalement est toujours exécuté, et c'est pourquoi il peut être utilisé à notre avantage en définissant toujours une valeur par défaut, donc en cas d'exception il est retourné, si nous ne voulons pas retourner cette valeur en cas d'exception, il est suffisant pour supprimer le bloc final. Btw, utiliser le passe sur une capture d'exception est quelque chose que je ne ferais jamais :)
Juan Antonio Gomez Moriano
@Juan Antonio Gomez Moriano, mon bloc de codage est à titre d'exemple uniquement. Je n'utiliserais probablement jamais le pass non plus
Greg
4

Chaque fois que vous voyez ceci:

try:
    y = 1 / x
except ZeroDivisionError:
    pass
else:
    return y

Ou même ceci:

try:
    return 1 / x
except ZeroDivisionError:
    return None

Considérez ceci à la place:

import contextlib
with contextlib.suppress(ZeroDivisionError):
    return 1 / x
Rajiv Bakulesh Shah
la source
1
Cela ne répond pas à ma question, car c'était simplement un exemple mon ami.
Juan Antonio Gomez Moriano
En Python, les exceptions ne sont pas des erreurs. Ils ne sont même pas exceptionnels. En Python, il est normal et naturel d'utiliser des exceptions pour le contrôle de flux. Cela est démontré par l'inclusion de contextlib.suppress () dans la bibliothèque standard. Voir la réponse de Raymond Hettinger ici: stackoverflow.com/a/16138864/1197429 (Raymond est un contributeur Python de base et une autorité sur tout ce qui est Pythonic!)
Rajiv Bakulesh Shah
4

Juste parce que personne d'autre n'a publié cette opinion, je dirais

éviter les elseclauses try/excepts parce qu'elles ne sont pas familières à la plupart des gens

Contrairement aux mots - clés try, exceptet finally, le sens de la elseclause est pas de soi; c'est moins lisible. Parce qu'il n'est pas utilisé très souvent, les personnes qui liront votre code voudront revérifier les documents pour s'assurer qu'elles comprennent ce qui se passe.

(J'écris cette réponse précisément parce que j'ai trouvé un try/except/elsedans ma base de code et cela a causé un moment wtf et m'a forcé à faire une recherche sur Google).

Donc, partout où je vois du code comme l'exemple OP:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
else:
    # do some more processing in non-exception case
    return something

Je préférerais refactoriser

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    return  # <1>
# do some more processing in non-exception case  <2>
return something
  • <1> retour explicite, montre clairement que, dans le cas exceptionnel, nous avons fini de travailler

  • <2> comme un effet secondaire mineur agréable, le code qui était dans le elsebloc est déduit d'un niveau.

hwjp
la source
1
Contre-argument de l'argument du diable: plus les gens l'utilisent, plus il deviendra bien adopté. Juste matière à réflexion, bien que je convienne que la lisibilité est importante. Cela dit, une fois que quelqu'un comprend try-else, je dirais que c'est beaucoup plus lisible que l'alternative dans de nombreux cas.
bob
2

Voici mon simple extrait sur la façon de comprendre le bloc try-except-else-finally en Python:

def div(a, b):
    try:
        a/b
    except ZeroDivisionError:
        print("Zero Division Error detected")
    else:
        print("No Zero Division Error")
    finally:
        print("Finally the division of %d/%d is done" % (a, b))

Essayons div 1/1:

div(1, 1)
No Zero Division Error
Finally the division of 1/1 is done

Essayons div 1/0

div(1, 0)
Zero Division Error detected
Finally the division of 1/0 is done
zakiakhmad
la source
1
Je pense que cela ne montre pas pourquoi vous ne pouvez pas simplement mettre le code else à l'intérieur de l'essai
Mojimi
-4

OP, VOUS ÊTES CORRECT. Le reste après try / except en Python est moche . cela conduit à un autre objet de contrôle de flux où aucun n'est nécessaire:

try:
    x = blah()
except:
    print "failed at blah()"
else:
    print "just succeeded with blah"

Un équivalent totalement clair est:

try:
    x = blah()
    print "just succeeded with blah"
except:
    print "failed at blah()"

C'est beaucoup plus clair qu'une clause else. Le reste après try / except n'est pas souvent écrit, donc cela prend un moment pour comprendre quelles sont les implications.

Ce n'est pas parce que vous POUVEZ faire quelque chose que vous DEVRIEZ faire quelque chose.

Beaucoup de fonctionnalités ont été ajoutées aux langues parce que quelqu'un pensait que cela pourrait être utile. Le problème, c'est que plus il y a de fonctionnalités, moins les choses sont claires et évidentes parce que les gens n'utilisent généralement pas ces cloches et ces sifflets.

Juste mes 5 cents ici. Je dois venir derrière et nettoyer beaucoup de code écrit par des développeurs de collège de première année qui pensent qu'ils sont intelligents et veulent écrire du code d'une manière ultra serrée et ultra efficace quand cela en fait un gâchis pour essayer de lire / modifier plus tard. Je vote pour la lisibilité tous les jours et deux fois le dimanche.

Kevin J. Rice
la source
15
Tu as raison. C'est totalement clair et équivalent ... sauf si c'est votre printdéclaration qui échoue. Que se passe-t-il si x = blah()renvoie un str, mais votre relevé d'impression l'est print 'just succeeded with blah. x == %d' % x? Vous avez maintenant TypeErrorgénéré un être où vous n'êtes pas prêt à en gérer un; vous inspectez x = blah()pour trouver la source de l'exception, et ce n'est même pas là. J'ai fait cela (ou l'équivalent) plus d'une fois où cela elsem'aurait empêché de faire cette erreur. Maintenant je sais mieux. :-D
Doug R.
2
... et oui, vous avez raison. La elseclause n'est pas une jolie déclaration, et tant que vous n'y êtes pas habituée, elle n'est pas intuitive. Mais alors, ce n'était pas le cas finallyquand j'ai commencé à l'utiliser ...
Doug R.
2
Pour faire écho à Doug R., ce n'est pas équivalent car les exceptions pendant les instructions de la elseclause ne sont pas prises en compte par le except.
alastair
si ... sauf ... else est plus lisible, sinon vous devez lire "oh, après le bloc try, et aucune exception, allez à l'instruction en dehors du bloc try", donc en utilisant else: tend à connecter un peu la syntaxe sémantiquement mieux imo. En outre, il est recommandé de laisser vos instructions non capturées en dehors du bloc try initial.
cowbert
1
@DougR. "vous inspectez x = blah()pour trouver la source de l'exception", tracebackVous vous demandez pourquoi vous inspecteriez la source de l'exception à un mauvais endroit?
nehem