@@ -0,0 +1,50 @@ | |||
Pile d'appels | |||
============= | |||
Reprenons un exemple de code qui provoque une erreur, par exemple en essayant | |||
de diviser par zéro:: | |||
def mauvaise_fonction(): | |||
return 1 / 0 | |||
def fonction_intermédiaire(): | |||
mauvaise_fonction() | |||
def fonction_principale(): | |||
fonction_intermédiaire() | |||
fonction_principale() | |||
Si on lance ce code, voilà ce qu'on obtient:: | |||
Traceback (most recent call last): | |||
File "mauvaises_maths.py", line 13, in <module> | |||
fonction_principale() | |||
File "mauvaises_maths.py", line 10, in fonction_principale | |||
fonction_intermédiaire() | |||
File "mauvaises_maths.py", line 6, in fonction_intermédiaire | |||
mauvaise_fonction() | |||
File "mauvaises_maths.py", line 2, in mauvaise_fonction | |||
return 1 / 0 | |||
ZeroDivisionError: division by zero | |||
Ceci s'appelle une *pile d'appels*. Elle permet de voir exactement par quelles fonction on est passé et | |||
dans quel ordre. Elle se lit de haut en bas: | |||
* On appelé `fonction_principale()` | |||
* Cette fonction a à son tour appelé `fonction_intermédiaire()` | |||
* `fonction_intermédiaire()` à appelé `mauvaise_fonction()` | |||
* `mauvaise_fonction()` a levé une exception | |||
Notez que chaque élément de la pile comprend: | |||
* le nom de la fonction | |||
* le chemin du module la contetant | |||
* le numéro et la ligne précise du code qui a été appelé | |||
Il est important de bien lire les piles d'appels quand on cherche | |||
à comprendre d'où vient une exception. | |||
Après la pile d'appels, on a le *nom* de l'exception et sa *description*. | |||
@@ -0,0 +1,75 @@ | |||
Exceptions natives | |||
================== | |||
Les exceptions sont toujours des instances de classes, et les classes d'exceptions héritent | |||
toujours de la class ``BaseException``. | |||
Le nom de l'exception est en réalité le nom de la classe, ici l'exception levée par la ligne | |||
``return 1 / 0`` est une instance de la classe ``ZeroDivisionError``. | |||
Cette exception fait partie des nombreuses exceptions préféfinies en Python. Ensemble, elles | |||
forment une *hiérarchie* dont voici un extrait: | |||
.. code-block:: text | |||
BaseException | |||
+-- SystemExit | |||
+-- KeyboardInterrupt | |||
+-- Exception | |||
+-- ArithmeticError | |||
| +-- ZeroDivisionError | |||
+-- LookupError | |||
| +-- IndexError | |||
| +-- KeyError | |||
+-- OSError | |||
| +-- FileNotFoundError | |||
+-- TypeError | |||
+-- ValueError | |||
IndexError et KeyError | |||
---------------------- | |||
``IndexError`` est levée quand on essaye d'accéder à un index trop grand | |||
dans une liste:: | |||
ma_liste = ["pomme"] | |||
ma_liste[2] = "abricot" | |||
# IndexError: list assignment index out of range | |||
``KeyError`` est levée quand on essaye d'accéder à une clé qui n'existe pas | |||
dans un dictionnaire:: | |||
scores = { "Alice" : 10 } | |||
score_de_bob = scores["Bob"] | |||
# KeyError: 'Bob' | |||
Notez que la description de ``KeyError`` est la valeur de la clé manquante. | |||
ValueError | |||
---------- | |||
``ValueError`` est levée (entre autres) quand on tente une maunvaise conversions:: | |||
entrée_utilisateur = "pas un nombre" | |||
valeu = int(entrée_utilisateur) | |||
KeyboardInterrupt | |||
----------------- | |||
``KeyboardInterrupt`` est levée quand on fait ``ctrl-c``. | |||
FileNotFoundError | |||
------------------ | |||
``FileNotFoundError`` est levée quand on essaye d'ouvrir | |||
en lecture un fichier qui n'exsiste pas:: | |||
with open("fichier-inexistant.txt", "r") as f: | |||
contenu = f.read() |
@@ -0,0 +1,101 @@ | |||
Gestion des exceptions | |||
====================== | |||
Bloc try/except | |||
--------------- | |||
On peut *gérer* (ou *attraper*) une exception en utilisant un bloc | |||
``try/except`` et le nom d'une classe d'exception:: | |||
try: | |||
a = 1 / 0 | |||
except ZeroDivisionError: | |||
print("Ouelqu'un a essayé de diviser par zéro!") | |||
# Affiche: Ouelqu'un a essayé de diviser par zéro! | |||
À noter : le bloc dans ``try`` s'interrompt dès que l'exception est levée, | |||
et on ne passe dans le bloc ``except`` que si une exception a effectivement | |||
été levée. | |||
.. code-block:: python | |||
x = 14 | |||
y = 0 | |||
try: | |||
z = x / y | |||
print("z vaut", z) | |||
except ZeroDivisionError: | |||
print("Ouelqu'un a essayé de diviser par zéro!") | |||
# Affiche: Ouelqu'un a essayé de diviser par zéro! | |||
Notez que la ligne ``print("z vaut", z)`` n'as pas été exécutée. | |||
Autr exemple: | |||
.. code-block:: python | |||
x = 14 | |||
y = 2 | |||
try: | |||
z = x / y | |||
print("z vaut", z) | |||
except ZeroDivisionError: | |||
print("Ouelqu'un a essayé de diviser par zéro!") | |||
# Affiche: 'z vaut 7.0' | |||
Notez que la ligne ``print("Ouelqu'un a essayé de diviser par zéro!")`` n'as pas été exécutée. | |||
Gestion de plusieurs exceptions | |||
-------------------------------- | |||
Le mot après ``except`` doit être celui d'une classe, et l'exception n'est gérée | |||
que si sa classe est **égale ou une fille** de celle ci. | |||
Par exemple, ceci fonctionne car ``ZeroDivisionError`` est bien une fille | |||
de la classe ``ArithmeticError``:: | |||
x = 14 | |||
y = 0 | |||
try: | |||
z = x / y | |||
print("z vaut", z) | |||
except ArithmeticError: | |||
print("Ouelqu'un a essayé une opération impossible") | |||
On peut aussi mettre plusieurs blocs de ``except``:: | |||
try: | |||
tente_un_truc_risqué() | |||
except ZeroDivisionError: | |||
print("raté : division par zéro!") | |||
except FileNotFoundError: | |||
print("raté : fichier non trouvé") | |||
Ou gérer des exception de classes différentes avec le même bloc:: | |||
try: | |||
tente_un_truc_risqué() | |||
except (ZeroDivisionError, FileNotFoundError) | |||
print("raté!") | |||
Accéder à la valeur de l'exception | |||
----------------------------------- | |||
On peut récupérer l'instance de l'exception levée avec ``as``:: | |||
try: | |||
ounver_fichier() | |||
except FileNotFoundError as e: | |||
print("le fichier: ", e.filename, "n'existe pa") | |||
Ici on utilise l'attribut ``filename`` de la classe ``FileNotFoundError`` | |||
pour afficher un message d'erreur |
@@ -0,0 +1,54 @@ | |||
Levée d'exceptions | |||
================== | |||
raise | |||
----- | |||
On peut lever explicitement un exception en appelant le mot-clé ``raise`` suivi | |||
d'une **instance** d'une classe. | |||
Par exemple en utilisant une exception native:: | |||
def dire_bonjour(prénom): | |||
if not prénom: | |||
raise ValueError("prénom vide") | |||
Définition d'exceptions à la carte | |||
----------------------------------- | |||
On peut ré-utiliser les exceptions natives, ou définir sa propre classe:: | |||
class OpérationImpossible(Exception): | |||
pass | |||
def ma_fonction(): | |||
if cas_impossible: | |||
raise OpérationImpossible() | |||
Gérer puis re-lever l'exception géré | |||
------------------------------------- | |||
Parfois il est utile de re-lever l'exception qu'on vient de géner. | |||
Dans ce cas, on utilise ``raise`` sans argument:: | |||
try: | |||
tente_un_truc_risqué() | |||
exeept ArithmeticError: | |||
... | |||
raise | |||
raise from | |||
---------- | |||
On peut donner une *cause directe* lorsqu'on lève un exception avec ``from``:: | |||
def appelle_maman(): | |||
numéro = répertoire["Maman"] | |||
try: | |||
appelle_maman() | |||
except KeyError as e: | |||
raise AppelImpossible from e | |||
@@ -0,0 +1,37 @@ | |||
Else et finally | |||
=============== | |||
else | |||
---- | |||
Si on rajoute un bloc ``else`` après le ``except``, le bloc n'est éxécuté que si | |||
*aucune* exception n'a été levée:: | |||
try: | |||
tente_un_truc_risqué() | |||
except (ZeroDivisionError, FileNotFoundError): | |||
print("raté") | |||
else: | |||
print("ouf - ça a marché") | |||
finally | |||
-------- | |||
Si on rajoute un bloc ``finally`` après le ``except``, le bloc est éxécuté *dans tous les cas*, | |||
qu'une exception ait été levée ou non. On s'en sert souvent pour "annuler" du code | |||
qui aurait été utilisé dans le bloc ``try``:: | |||
personnage = Personnage() | |||
try: | |||
personnage.entre_en_scène() | |||
personnage.tente_un_truc_risqué() | |||
except ZeroDivisionError: | |||
print("raté") | |||
finally: | |||
personnage.quitte_la_scène() | |||
Si dans le bloc ``try`` une exception **différente** de ``ZeroDivisionError`` est | |||
levée, on passera *quand même* dans le bloc ``finally``, *puis* l'exception sera | |||
levée à nouveau. |
@@ -0,0 +1,16 @@ | |||
Chapitre 19 - Exceptions | |||
======================== | |||
On a souvent montré des exemples de code qui provoquaient des erreurs au | |||
cours des chapitres précédents. Il est temps maintenant de se pencher | |||
davantage sur celles-ci. | |||
.. toctree:: | |||
:maxdepth: 1 | |||
./01-piles.rst | |||
./02-exceptions-natives.rst | |||
./03-gestion.rst | |||
./04-levée.rst | |||
./05-else-finally.rst | |||
@@ -0,0 +1,18 @@ | |||
class Personnage: | |||
def tente_un_truc_risqué(self): | |||
1 / 0 | |||
def quitte_la_scène(self): | |||
print("je m'en vais") | |||
def entre_en_scène(): | |||
return Personnage() | |||
personnage = entre_en_scène() | |||
try: | |||
personnage.tente_un_truc_risqué() | |||
except FileNotFoundError: | |||
print("raté") | |||
personnage.quitte_la_scène() |
@@ -35,3 +35,4 @@ remarques. | |||
16-interpréteur-interactif/index | |||
17-classes-03/index | |||
18-functions-02/index | |||
19-exceptions/index |