@@ -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 | 16-interpréteur-interactif/index | ||||
17-classes-03/index | 17-classes-03/index | ||||
18-functions-02/index | 18-functions-02/index | ||||
19-exceptions/index |