web-dev-qa-db-fra.com

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

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 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, si cela est inclus dans la langue, il doit y avoir une bonne raison pour cela, n'est-ce pas?

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

Normalement, je traite 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

"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 Python principaux utilisent des exceptions pour le contrôle de flux et ce style est fortement intégré dans le langage (le protocole itérateur utilise StopIteration pour terminer la boucle de signal).

De plus, le style try-except est utilisé pour éviter les conditions de concurrence inhérentes à certaines des constructions "look-before-you-jump" . Par exemple, si vous testez os.path.exists , les informations risquent d’être obsolètes au moment de votre utilisation. il. De même, Queue.full renvoie des informations éventuellement obsolètes. Le style try-except-else produira un code plus fiable dans ces cas.

"Si je comprends bien, les exceptions ne sont pas des erreurs, elles ne devraient ê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 contrôle-flux. De plus, l'utilisation d'exceptions dans Python ne ralentit pas le code environnant ni le code appelant comme c'est le cas dans certains langages compilés (ie CPython implémente déjà du code pour la vérification des exceptions à chaque étape, que vous utilisiez réellement des exceptions ou non).

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

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

En plus de contribuer à éviter les conditions de concurrence, les exceptions sont également très utiles pour extraire les boucles en dehors de la gestion des erreurs. Ceci est une optimisation nécessaire dans les langages interprétés qui n'ont pas tendance à avoir un mouvement automatique mouvement de code invariant par la boucle .

En outre, des exceptions peuvent considérablement simplifier le code dans des situations courantes où la capacité de gérer un problème est très éloignée de celle où le problème s'est posé. 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 rencontrées dans les routines de bas niveau (telles que les enregistrements dupliqués pour des clés uniques dans des accès à une base de données) ne peuvent être gérées que dans le code de niveau supérieur (par exemple, demander à l'utilisateur une nouvelle clé n'entrant 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 découplées de cet aspect du contrôle de flux.

Il y a un le blog de Nice sur l'indispensabilité des exceptions ici .

Voir aussi cette réponse au dépassement de pile: 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 fonctionne quand il n'y a pas d'exception mais avant la clause finally. C'est son objectif principal.

Sans la clause else, la seule option permettant d'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 générer des exceptions dans du code qui n'était pas destiné à être protégé par le bloc try.

Le cas d'utilisation de l'exécution de code non protégé supplémentaire avant la finalisation ne se produit pas très souvent. Donc, ne vous attendez pas à voir de nombreux exemples dans le code publié. C'est un peu 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 plus modestes:

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 concerne un peu d'embellissement (alignement des résultats exceptionnels et des résultats non exceptionnels au même niveau d'indentation). Cette utilisation est toujours facultative et n'est pas strictement nécessaire.

601
Raymond Hettinger

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

Un bloc try vous permet de traiter une erreur attendue. Le bloc except devrait capturer uniquement les exceptions que vous êtes prêt à gérer. Si vous gérez une erreur inattendue, votre code peut être incorrect et masquer les bogues.

Une clause else sera exécutée s'il n'y a pas d'erreur et si vous n'exécutez pas ce code dans le bloc try, vous évitez de détecter une erreur inattendue. Encore une fois, attraper une erreur inattendue peut cacher des bugs.

Exemple

Par exemple:

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

La suite "try, except" comporte deux clauses facultatives, else et finally. Donc, c'est en fait try-except-else-finally.

else n'évaluera que s'il n'y a pas d'exception du bloc try. 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 (ce qui pourrait créer des bugs), nous constatons qu'il réduit les lignes de code et que nous pouvons avoir une base de code plus lisible, maintenable et moins boguée.

finally

finally sera exécuté à tout moment, même si une autre ligne est évaluée avec une instruction return.

En panne avec pseudo-code

Il pourrait être utile de ventiler cette information, sous la forme la plus petite possible, avec toutes les fonctionnalités, avec des commentaires. Supposez que ce pseudo-code est syntaxiquement correct (mais non exécutable à moins que les noms ne soient définis) est 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 bloc else du bloc try, où il serait exécuté si il n'y avait pas d'exception, mais que se passe-t-il si ce code lui-même soulève une exception du genre de celle que nous détectons? Le laisser dans le bloc try cacherait ce bogue.

Nous voulons minimiser les lignes de code dans le bloc try afin d'éviter de capturer des exceptions inattendues, en partant du principe que si notre code échoue, nous voulons qu'il échoue bruyamment. Ceci est un meilleure pratique .

Je crois comprendre que les exceptions ne sont pas des erreurs

En Python, la plupart des exceptions sont des erreurs.

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

$ python -m pydoc exceptions

ou Python 3:

$ python -m pydoc builtins

Va nous donner la hiérarchie. Nous pouvons voir que la plupart des types de Exception sont des erreurs, bien que Python en utilise certaines pour des choses comme la fin des boucles for (StopIteration). Ceci est 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 commande ping à une API externe et que vous voulez gérer l'exception au sein d'une classe en dehors de l'encapsuleur de l'API, renvoyez-vous simplement e depuis la méthode sous la clause except où e est l'objet exception?

Non, vous ne retournez pas l'exception, il suffit de la relancer avec un raise dépouillé pour préserver le stacktrace.

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

Ou bien, dans Python 3, vous pouvez déclencher une nouvelle exception et conserver la trace en arrière avec le chaînage des exceptions:

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

Je précise dans ma réponse ici .

153
Aaron Hall

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 'demandez pardon, pas la permission' . Cela signifie que l'utilisation d'exceptions dans le cadre de votre contrôle de flux est parfaitement acceptable et même encouragée.

C’est généralement une bonne chose, car cette méthode permet d’éviter certains problèmes (par exemple, les conditions de concurrence sont souvent évitées), et tend à rendre le code un peu plus lisible.

Imaginons que vous preniez certaines entrées utilisateur qui doivent être traitées, mais que vous utilisiez une valeur par défaut qui est déjà traitée. La structure try: ... except: ... else: ... permet un 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 à 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 ceci".

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

33
Gareth Latty

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

La réponse à cela est que cela 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 méthode dict.get:

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

Le bloc try/except est une manière beaucoup plus visuellement plus encombrée et verbeuse d'écrire ce qui peut être efficacement exécuté dans une seule ligne avec une méthode atomique. Il existe d'autres cas où cela est vrai.

Cependant, cela ne signifie pas que nous devrions éviter toute gestion des exceptions. Dans certains cas, il est préférable d'éviter les conditions de compétition. Ne vérifiez pas si un fichier existe, essayez simplement de l'ouvrir et récupérez l'erreur IOError appropriée. Par souci de simplicité et de lisibilité, essayez de l’encapsuler ou de l’analyser comme un apropos.

Lisez le Zen of Python , sachant qu'il existe des principes en tension et méfiez-vous du dogme qui repose trop lourdement sur l'une des affirmations qu'il contient.

15
Aaron Hall

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.
10
Algebra

Vous devez faire attention à l'utilisation du bloc finally, car ce n'est pas la même chose que d'utiliser un bloc else dans l'essai, à l'exception de. Le bloc finally sera exécuté quel que soit le 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 améliore la lisibilité de votre code 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"
   ....:
5
Greg

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
4
Rajiv Bakulesh Shah

Voici mon extrait de code expliquant comment 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
2
zakiakhmad