diff --git a/cours/source/19-exceptions/01-piles.rst b/cours/source/19-exceptions/01-piles.rst new file mode 100644 index 0000000..2bbc448 --- /dev/null +++ b/cours/source/19-exceptions/01-piles.rst @@ -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 + 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*. + diff --git a/cours/source/19-exceptions/02-exceptions-natives.rst b/cours/source/19-exceptions/02-exceptions-natives.rst new file mode 100644 index 0000000..de32d91 --- /dev/null +++ b/cours/source/19-exceptions/02-exceptions-natives.rst @@ -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() diff --git a/cours/source/19-exceptions/03-gestion.rst b/cours/source/19-exceptions/03-gestion.rst new file mode 100644 index 0000000..8ca5294 --- /dev/null +++ b/cours/source/19-exceptions/03-gestion.rst @@ -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 diff --git a/cours/source/19-exceptions/04-levée.rst b/cours/source/19-exceptions/04-levée.rst new file mode 100644 index 0000000..6d50cb3 --- /dev/null +++ b/cours/source/19-exceptions/04-levée.rst @@ -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 + diff --git a/cours/source/19-exceptions/05-else-finally.rst b/cours/source/19-exceptions/05-else-finally.rst new file mode 100644 index 0000000..d73d16a --- /dev/null +++ b/cours/source/19-exceptions/05-else-finally.rst @@ -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. diff --git a/cours/source/19-exceptions/index.rst b/cours/source/19-exceptions/index.rst new file mode 100644 index 0000000..22d72e1 --- /dev/null +++ b/cours/source/19-exceptions/index.rst @@ -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 + diff --git a/cours/source/19-exceptions/yo.py b/cours/source/19-exceptions/yo.py new file mode 100644 index 0000000..c7a16f3 --- /dev/null +++ b/cours/source/19-exceptions/yo.py @@ -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() diff --git a/cours/source/index.rst b/cours/source/index.rst index 8f81d0e..5eeb214 100644 --- a/cours/source/index.rst +++ b/cours/source/index.rst @@ -35,3 +35,4 @@ remarques. 16-interpréteur-interactif/index 17-classes-03/index 18-functions-02/index + 19-exceptions/index