Essayer de coder ← Retour aux sujets

try / except

gérer les erreurs

Anticiper les erreurs : intercepter une exception, nettoyer avec finally, la signaler avec raise.

try:
Le code qui peut échouer
except ValueError:
Réagir à un type d'erreur précis
else:
S'exécute si le try s'est bien passé
finally:
Toujours exécuté (nettoyage, fermeture)
raise RuntimeError("msg")
Signaler une erreur soi-même
assert condition
Vérifier une hypothèse (lève AssertionError)
conversion.py
try:
    n = int("abc")
except ValueError:
    print("Pas un entier")
Affichage
Pas un entier
💡 Context manager

with open(...) as f: ferme le fichier automatiquement, même quand une exception se produit dans le bloc.

Les exceptions à connaître

types courants

Chaque erreur a un type qui dit ce qui ne va pas : on intercepte le type qu'on sait traiter.

ValueError
Bonne nature de valeur, mais contenu invalide : int("abc")
TypeError
Opération sur un mauvais type : "a" + 1
IndexError KeyError
Indice hors liste ; clé absente d'un dictionnaire
ZeroDivisionError
Division ou modulo par zéro
NameError AttributeError
Nom inconnu ; attribut ou méthode inexistant
FileNotFoundError
Fichier introuvable à l'ouverture
types_erreurs.py
for texte in ["12", "x", ""]:
    try:
        print(10 // int(texte))
    except ValueError:
        print("valeur invalide")
Affichage
0
valeur invalide
valeur invalide

⚠️ Le cours : les exceptions

Quand quelque chose d'anormal se produit pendant l'exécution (division par zéro, fichier introuvable, conversion impossible…), Python lève une exception : il interrompt le déroulement normal et remonte les appels de fonctions à la recherche d'un code qui sait la traiter. Si personne ne l'intercepte, le programme s'arrête en affichant une trace (*traceback*) : la liste des appels qui ont mené à l'erreur, et, à la fin, le type et le message de l'exception.

trace.py
def diviser(a, b):
    return a / b

print(diviser(6, 3))
print(diviser(1, 0))
Ce qui s'affiche
2.0
Traceback (most recent call last):
  File "trace.py", line 5, in <module>
    print(diviser(1, 0))
  File "trace.py", line 2, in diviser
    return a / b
ZeroDivisionError: division by zero

Pour lire une trace, on commence par la dernière ligne (le type et le message de l'erreur), puis on remonte pour trouver la ligne de ton code qui l'a provoquée.

Les exceptions les plus courantes

ExceptionQuand ?Exemple
ValueErrorla valeur a le bon type mais un contenu invalideint("abc")
TypeErroropération sur des types incompatibles"a" + 1 len(5)
IndexErrorindice hors d'une séquence[1, 2][5]
KeyErrorclé absente d'un dictionnaire{"a": 1}["b"]
ZeroDivisionErrordivision ou modulo par zéro1 / 0 5 % 0
NameErrornom de variable inconnuprint(x) sans avoir défini x
AttributeErrorattribut ou méthode inexistant"a".ajouter(1)
FileNotFoundErrorfichier introuvableopen("absent.txt")
ImportError ModuleNotFoundErrormodule introuvableimport modulequinexistepas
RecursionErrortrop d'appels récursifs imbriquésfonction récursive sans cas de base
declencher.py
erreurs = [
    lambda: int("abc"),
    lambda: "a" + 1,
    lambda: [1, 2][5],
    lambda: {"a": 1}["b"],
    lambda: 1 / 0,
    lambda: "a".ajouter(1),
]
for provoquer in erreurs:
    try:
        provoquer()
    except Exception as e:
        print(type(e).__name__)
Affichage
ValueError
TypeError
IndexError
KeyError
ZeroDivisionError
AttributeError

try et except : intercepter une exception

On place le code à risque dans un bloc try. Si une exception se produit, l'exécution saute au premier bloc except dont le type correspond ; sinon, les blocs except sont ignorés. Le programme continue ensuite normalement après le try. Avec as e, on récupère l'objet exception : str(e) donne son message.

try_except.py
def lire_entier(texte):
    try:
        return int(texte)
    except ValueError as e:
        print("erreur :", e)
        return None

print(lire_entier("42"))
print(lire_entier("4x2"))
Affichage
42
erreur : invalid literal for int() with base 10: '4x2'
None

Plusieurs except : du plus précis au plus général

On peut enchaîner plusieurs blocs except pour réagir différemment selon le type, ou regrouper plusieurs types dans un tuple. Python examine les blocs dans l'ordre : il faut donc placer les exceptions précises avant les générales (Exception capture presque tout). Une except trop large peut cacher des erreurs qu'on ne voulait pas traiter.

plusieurs_except.py
def calculer(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return "division par zéro"
    except (TypeError, ValueError):
        return "valeurs invalides"

print(calculer(6, 3))
print(calculer(1, 0))
print(calculer("1", 2))
Affichage
2.0
division par zéro
valeurs invalides

else et finally

Le bloc else s'exécute seulement si le try s'est terminé sans exception : il contient la suite du code qui dépend du succès, sans qu'une erreur de cette suite soit interceptée par erreur. Le bloc finally s'exécute dans tous les cas (succès, exception interceptée, exception non interceptée, return) : c'est l'endroit pour libérer une ressource.

else_finally.py
def diviser(a, b):
    try:
        resultat = a / b
    except ZeroDivisionError:
        print("division par zéro")
    else:
        print("résultat :", resultat)
    finally:
        print("fin de l'opération")

diviser(6, 3)
diviser(1, 0)
Affichage
résultat : 2.0
fin de l'opération
division par zéro
fin de l'opération

raise : signaler une erreur

raise ValueError("message") lève volontairement une exception : une fonction l'utilise pour refuser des valeurs invalides au lieu de renvoyer un résultat absurde. Un raise seul, dans un bloc except, relance l'exception en cours (après l'avoir, par exemple, journalisée). raise NouvelleErreur(...) from e enchaîne deux erreurs en gardant la cause d'origine.

raise.py
def racine_entiere(n):
    if n < 0:
        raise ValueError("n doit être positif")
    return int(n ** 0.5)

print(racine_entiere(49))
try:
    racine_entiere(-4)
except ValueError as e:
    print("Erreur :", e)

def lire(texte):
    try:
        return int(texte)
    except ValueError as e:
        raise RuntimeError("lecture impossible") from e

try:
    lire("x")
except RuntimeError as e:
    print(e, "| cause :", type(e.__cause__).__name__)
Affichage
7
Erreur : n doit être positif
lecture impossible | cause : ValueError

Créer ses propres exceptions

Une exception personnalisée est une classe qui hérite d'Exception (ou d'une de ses filles). Elle donne un nom précis à une erreur du domaine du programme (SoldeInsuffisantError) et peut transporter des informations dans ses attributs.

exception_perso.py
class SoldeInsuffisantError(Exception):
    def __init__(self, solde, montant):
        super().__init__(f"solde {solde} < montant {montant}")
        self.manque = montant - solde

def retirer(solde, montant):
    if montant > solde:
        raise SoldeInsuffisantError(solde, montant)
    return solde - montant

print(retirer(100, 30))
try:
    retirer(50, 80)
except SoldeInsuffisantError as e:
    print(e, "| manque :", e.manque)
Affichage
70
solde 50 < montant 80 | manque : 30

La hiérarchie des exceptions

Les exceptions forment une hiérarchie : intercepter une classe intercepte aussi toutes ses filles. ArithmeticError regroupe ZeroDivisionError et OverflowError ; LookupError regroupe IndexError et KeyError ; Exception est la mère de presque toutes les erreurs « normales ».

BaseException
 ├── SystemExit, KeyboardInterrupt        (arrêt du programme : on ne les intercepte pas)
 └── Exception
      ├── ArithmeticError  ─ ZeroDivisionError, OverflowError
      ├── LookupError      ─ IndexError, KeyError
      ├── OSError          ─ FileNotFoundError, PermissionError
      ├── ValueError
      ├── TypeError
      ├── NameError
      ├── AttributeError
      ├── ImportError      ─ ModuleNotFoundError
      ├── RuntimeError     ─ RecursionError, NotImplementedError
      └── AssertionError
hierarchie.py
print(issubclass(ZeroDivisionError, ArithmeticError))
print(issubclass(KeyError, LookupError), issubclass(KeyError, ValueError))
try:
    {"a": 1}["b"]
except LookupError as e:
    print("capturée par LookupError :", type(e).__name__)
Affichage
True
True False
capturée par LookupError : KeyError

with : fermer proprement une ressource

L'instruction with utilise un gestionnaire de contexte : elle garantit que la ressource (fichier, connexion) est libérée à la sortie du bloc, même si une exception s'y produit. C'est l'équivalent d'un try / finally écrit pour toi.

with.py
try:
    with open("inexistant.txt") as f:
        contenu = f.read()
except FileNotFoundError:
    print("fichier introuvable")

with open("ok.txt", "w") as f:
    f.write("bonjour")
print(f.closed)
Affichage
fichier introuvable
True

assert : vérifier ce qu'on croit vrai

assert condition, "message" lève une AssertionError si la condition est fausse. On s'en sert pour tester une fonction et pour documenter une hypothèse du programme ; ce n'est pas un moyen de valider des saisies d'utilisateur (les assert peuvent être désactivés avec l'option -O).

assert.py
def moyenne(notes):
    assert len(notes) > 0, "la liste est vide"
    return sum(notes) / len(notes)

print(moyenne([12, 15]))
try:
    moyenne([])
except AssertionError as e:
    print("AssertionError :", e)
Affichage
13.5
AssertionError : la liste est vide

Demander pardon ou demander la permission ?

Il y a deux styles. LBYL (*look before you leap*) : on teste avant d'agir (if cle in d:). EAFP (*easier to ask forgiveness than permission*) : on essaie, et on traite l'erreur si elle survient. Python encourage souvent l'EAFP, surtout quand le test préalable est compliqué ou peu sûr (un fichier peut disparaître entre le test et l'ouverture).

eafp.py
stock = {"pommes": 4, "poires": 0}

def disponible_lbyl(produit):
    return produit in stock and stock[produit] > 0

def disponible_eafp(produit):
    try:
        return stock[produit] > 0
    except KeyError:
        return False

print(disponible_lbyl("kiwis"), disponible_eafp("kiwis"), disponible_eafp("pommes"))
Affichage
False False True

Réessayer ou ignorer une valeur invalide

Dans une boucle, try / except permet de continuer malgré une donnée invalide, ou de redemander une valeur jusqu'à ce qu'elle convienne.

boucle_try.py
valeurs = ["12", "x", "7", "", "3"]
total = 0
ignorees = 0
for v in valeurs:
    try:
        total += int(v)
    except ValueError:
        ignorees += 1
print(total, ignorees)

saisies = iter(["abc", "-3", "25"])
while True:
    try:
        age = int(next(saisies))
        if age < 0:
            raise ValueError("âge négatif")
        break
    except ValueError as e:
        print("refusé :", e)
print("âge accepté :", age)
Affichage
22 2
refusé : invalid literal for int() with base 10: 'abc'
refusé : âge négatif
âge accepté : 25

⚠️ Erreurs fréquentes

Un except sans type, ou trop large

except: (sans type) intercepte tout, y compris KeyboardInterrupt (le Ctrl + C de l'utilisateur) et les fautes de frappe du programmeur. Intercepte le type précis que tu sais traiter ; au pire except Exception.

À éviter
faux.py
try:
    resultat = calculer(donnees)
except:
    pass
Correct
correct.py
try:
    resultat = calculer(donnees)
except ZeroDivisionError:
    resultat = 0

Avaler l'erreur en silence

except ...: pass fait disparaître le problème et l'information qui permettrait de le comprendre. Au minimum, affiche le message ; mieux, traite réellement l'erreur ou laisse-la remonter.

À éviter
faux.py
try:
    n = int("abc")
except ValueError:
    pass
print(n)
Erreur
NameError: name 'n' is not defined
Correct
correct.py
try:
    n = int("abc")
except ValueError:
    n = 0
print(n)
Affichage
0

L'ordre des except

Python prend le premier bloc qui convient. Si except Exception est écrit avant except ZeroDivisionError, ce dernier ne sera jamais exécuté.

À éviter
faux.py
try:
    1 / 0
except Exception:
    print("erreur quelconque")
except ZeroDivisionError:
    print("division par zéro")
Affichage
erreur quelconque
Correct
correct.py
try:
    1 / 0
except ZeroDivisionError:
    print("division par zéro")
except Exception:
    print("erreur quelconque")
Affichage
division par zéro

Mettre trop de code dans le try

Un try doit contenir seulement l'instruction qui peut échouer. Si on y met tout un calcul, une erreur sans rapport (une faute de frappe, un autre ValueError) sera interceptée et traitée à tort.

À éviter
faux.py
try:
    n = int("12")
    resultat = 100 / n
    print("résultat :", resultat + total)
except ValueError:
    print("entier invalide")
Erreur
NameError: name 'total' is not defined
Correct
correct.py
try:
    n = int("12")
except ValueError:
    print("entier invalide")
else:
    print("résultat :", 100 / n)
Affichage
résultat : 8.333333333333334

Un return dans finally

Un return placé dans un bloc finally écrase toute valeur renvoyée ou toute exception en cours : l'erreur disparaît sans bruit. Réserve finally au nettoyage.

return_finally.py
def f():
    try:
        return "du try"
    finally:
        return "du finally"

print(f())
Affichage
du finally

Oublier de convertir une saisie

input() renvoie un texte : la plupart des TypeError et ValueError débutants viennent d'un calcul sur un texte non converti. Convertis avec int() / float() dans un try, et redemande en cas d'erreur.

À éviter
faux.py
age = "17"
print(age + 1)
Erreur
TypeError: can only concatenate str (not "int") to str
Correct
correct.py
age = "17"
try:
    print(int(age) + 1)
except ValueError:
    print("âge invalide")
Affichage
18

raise sans exception en cours

Un raise seul n'a de sens que dans un bloc except (il relance l'exception courante). Ailleurs, il provoque une RuntimeError.

raise_seul.py
try:
    raise
except RuntimeError as e:
    print(e)
Affichage
No active exception to reraise

📋 Aide-mémoire

La syntaxe

BesoinSyntaxe
Intercepter un typetry: ... puis except ValueError: ...
Récupérer l'exceptionexcept ValueError as e: puis str(e), repr(e), e.args, type(e).__name__
Plusieurs typesexcept (ValueError, TypeError): ou plusieurs blocs except (du plus précis au plus général)
Si pas d'erreurelse: après les except
Dans tous les casfinally: (nettoyage)
Lever une exceptionraise ValueError("message")
Relancerraise (dans un except)  ; raise Autre("...") from e (enchaîner)
Vérifierassert condition, "message"
Exception persoclass MonErreur(Exception): ... puis raise MonErreur("...")
Ignorer un type précisfrom contextlib import suppress puis with suppress(ValueError): ...
Ressource fermée à coup sûrwith open("f.txt") as f:
Afficher la traceimport traceback puis traceback.print_exc() ; logging.exception("message")

Les exceptions prédéfinies courantes

FamilleExceptions
Valeurs et typesValueError TypeError OverflowError
AccèsIndexError KeyError AttributeError NameError
CalculZeroDivisionError OverflowError
Fichiers et systèmeFileNotFoundError PermissionError OSError IOError
ModulesImportError ModuleNotFoundError
ExécutionRuntimeError NotImplementedError RecursionError MemoryError AssertionError
SyntaxeSyntaxError IndentationError (détectées avant l'exécution)
ArrêtKeyboardInterrupt (Ctrl + C) SystemExit (sys.exit())
  • Un try court, un except précis, un message utile : jamais de except: nu, jamais de pass muet.
  • else pour la suite qui dépend du succès, finally pour libérer une ressource (ou utilise with).
  • Pour lire une trace : dernière ligne d'abord (type et message), puis remonte jusqu'à ta ligne de code.
  • raise signale une erreur aux appelants : préfère lever une exception plutôt que renvoyer une valeur « magique » (-1, None).