Browse Source

Ajout d'un chapitre sur les exceptions

Work in progress ...
master
Dimitri Merejkowsky 4 years ago
parent
commit
2fcb1696e2
8 changed files with 352 additions and 0 deletions
  1. +50
    -0
      cours/source/19-exceptions/01-piles.rst
  2. +75
    -0
      cours/source/19-exceptions/02-exceptions-natives.rst
  3. +101
    -0
      cours/source/19-exceptions/03-gestion.rst
  4. +54
    -0
      cours/source/19-exceptions/04-levée.rst
  5. +37
    -0
      cours/source/19-exceptions/05-else-finally.rst
  6. +16
    -0
      cours/source/19-exceptions/index.rst
  7. +18
    -0
      cours/source/19-exceptions/yo.py
  8. +1
    -0
      cours/source/index.rst

+ 50
- 0
cours/source/19-exceptions/01-piles.rst View File

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


+ 75
- 0
cours/source/19-exceptions/02-exceptions-natives.rst View File

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

+ 101
- 0
cours/source/19-exceptions/03-gestion.rst View File

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

+ 54
- 0
cours/source/19-exceptions/04-levée.rst View File

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


+ 37
- 0
cours/source/19-exceptions/05-else-finally.rst View File

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

+ 16
- 0
cours/source/19-exceptions/index.rst View File

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


+ 18
- 0
cours/source/19-exceptions/yo.py View File

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

+ 1
- 0
cours/source/index.rst View File

@@ -35,3 +35,4 @@ remarques.
16-interpréteur-interactif/index
17-classes-03/index
18-functions-02/index
19-exceptions/index