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.
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.
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.
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 .
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.
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.
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.
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"
....:
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
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