| @@ -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 | |||