Python Try Except : Comment gérer les exceptions correctement
Updated on
Votre script Python lit un fichier, analyse les données et les envoie à une API. Il fonctionne parfaitement sur votre machine. Puis il s'exécute sur un serveur où le chemin du fichier est incorrect, le JSON est mal formé ou le réseau est en panne. Le programme plante avec un traceback et tout le pipeline s'arrête. C'est le problème que try/except résout. Au lieu de laisser les erreurs tuer votre programme, vous les interceptez, les gérez et continuez l'exécution.
Qu'est-ce qu'une exception en Python ?
Une exception est un événement qui perturbe le flux normal d'un programme. Quand Python rencontre une opération qu'il ne peut pas effectuer -- division par zéro, accès à une clé de dictionnaire manquante, ouverture d'un fichier inexistant -- il crée un objet exception et arrête l'exécution. Si rien n'intercepte cette exception, le programme se termine et affiche un traceback.
# This crashes the program
result = 10 / 0Sortie :
Traceback (most recent call last):
File "example.py", line 2, in <module>
result = 10 / 0
ZeroDivisionError: division by zeroLes exceptions sont différentes des erreurs de syntaxe. Une erreur de syntaxe signifie que Python ne peut pas du tout analyser votre code. Une exception se produit pendant l'exécution, après que le code a été analysé avec succès.
Syntaxe de base Try/Except
Le bloc try/except vous permet de tenter du code qui pourrait échouer et de définir ce qui se passe en cas d'échec.
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero.")Sortie :
Cannot divide by zero.Le programme ne plante pas. Python exécute le code à l'intérieur de try. Quand ZeroDivisionError se produit, l'exécution saute au bloc except. Tout ce qui suit le try/except continue normalement.
Vous pouvez également capturer l'objet exception pour inspecter le message d'erreur :
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Error: {e}")Sortie :
Error: division by zeroIntercepter des exceptions spécifiques
Python possède des dizaines de types d'exceptions intégrées. Intercepter le bon type rend votre code précis. Voici les exceptions les plus courantes que vous gérerez.
ValueError
Levée quand une fonction reçoit un argument du bon type mais avec une valeur inappropriée.
try:
number = int("not_a_number")
except ValueError as e:
print(f"Invalid value: {e}")Sortie :
Invalid value: invalid literal for int() with base 10: 'not_a_number'TypeError
Levée quand une opération est appliquée à un objet du mauvais type.
try:
result = "hello" + 42
except TypeError as e:
print(f"Type error: {e}")Sortie :
Type error: can only concatenate str (not "int") to strFileNotFoundError
Levée quand vous essayez d'ouvrir un fichier qui n'existe pas.
try:
with open("nonexistent_file.txt", "r") as f:
content = f.read()
except FileNotFoundError:
print("File not found. Check the file path.")KeyError
Levée quand vous accédez à une clé de dictionnaire qui n'existe pas.
data = {"name": "Alice", "age": 30}
try:
email = data["email"]
except KeyError as e:
print(f"Missing key: {e}")Sortie :
Missing key: 'email'IndexError
Levée quand vous accédez à un index de liste hors limites.
items = [10, 20, 30]
try:
value = items[5]
except IndexError:
print("Index out of range.")Blocs Except multiples
Vous pouvez gérer différents types d'exceptions avec des blocs except séparés. Python les vérifie dans l'ordre et exécute la première correspondance.
def parse_config(raw_value):
try:
parts = raw_value.split(":")
key = parts[0]
value = int(parts[1])
return {key: value}
except IndexError:
print("Config format error: missing colon separator.")
except ValueError:
print("Config format error: value is not a number.")
except AttributeError:
print("Config format error: input is not a string.")
parse_config("timeout:30") # Returns {'timeout': 30}
parse_config("timeout") # Config format error: missing colon separator.
parse_config("timeout:abc") # Config format error: value is not a number.
parse_config(12345) # Config format error: input is not a string.Vous pouvez également intercepter plusieurs exceptions dans un seul bloc except en utilisant un tuple :
try:
value = int(input("Enter a number: "))
result = 100 / value
except (ValueError, ZeroDivisionError) as e:
print(f"Invalid input: {e}")Le bloc Else
Le bloc else s'exécute uniquement quand aucune exception ne se produit dans le bloc try. Il sépare le code du "chemin heureux" du code de gestion des erreurs.
try:
number = int("42")
except ValueError:
print("That is not a valid number.")
else:
print(f"Successfully parsed: {number}")Sortie :
Successfully parsed: 42Le bloc else est utile car toute exception levée à l'intérieur n'est pas interceptée par les blocs except précédents. Cela empêche de masquer accidentellement des bugs dans votre code de chemin de succès.
filename = "data.txt"
try:
f = open(filename, "r")
except FileNotFoundError:
print(f"File '{filename}' does not exist.")
else:
content = f.read()
f.close()
print(f"Read {len(content)} characters.")Si open() échoue, le bloc except le gère. Si open() réussit, le bloc else lit le fichier. Toute erreur pendant f.read() n'est pas interceptée ici -- elle se propage vers le haut, ce qui est le comportement correct.
Le bloc Finally
Le bloc finally s'exécute toujours, qu'une exception se soit produite ou non. C'est l'endroit approprié pour le code de nettoyage : fermer des fichiers, libérer des verrous, se déconnecter des bases de données.
f = None
try:
f = open("data.txt", "r")
content = f.read()
except FileNotFoundError:
print("File not found.")
finally:
if f is not None:
f.close()
print("File handle closed.")Le bloc finally s'exécute même si le bloc try retourne une valeur ou lève une exception non gérée.
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
finally:
print("Division attempted.")
result = divide(10, 3)
# Output: Division attempted.
# result = 3.3333333333333335
result = divide(10, 0)
# Output: Division attempted.
# result = NoneLe patron complet Try/Except/Else/Finally
Voici comment les quatre blocs fonctionnent ensemble :
import json
def load_config(filepath):
"""Load and parse a JSON config file."""
f = None
try:
f = open(filepath, "r")
data = json.load(f)
except FileNotFoundError:
print(f"Config file '{filepath}' not found.")
return {}
except json.JSONDecodeError as e:
print(f"Invalid JSON in '{filepath}': {e}")
return {}
else:
print(f"Config loaded successfully with {len(data)} keys.")
return data
finally:
if f is not None:
f.close()
print("File handle released.")
config = load_config("settings.json")| Bloc | Quand il s'exécute | Objectif |
|---|---|---|
try | Toujours (en premier) | Contient le code qui pourrait lever une exception |
except | Seulement quand une exception correspondante se produit | Gère l'erreur |
else | Seulement quand aucune exception ne se produit dans try | Exécute la logique de succès |
finally | Toujours (en dernier) | Code de nettoyage qui doit s'exécuter quoi qu'il arrive |
Référence des exceptions intégrées courantes
Python inclut une hiérarchie d'exceptions intégrées. Voici celles que vous rencontrerez le plus souvent.
| Exception | Quand elle se produit | Exemple |
|---|---|---|
Exception | Classe de base pour la plupart des exceptions | Parent de toutes les exceptions non-system-exiting |
ValueError | Mauvaise valeur pour le type correct | int("abc") |
TypeError | Mauvais type pour une opération | "text" + 5 |
KeyError | Clé de dictionnaire manquante | d["missing"] |
IndexError | Index de liste hors limites | [1,2,3][10] |
FileNotFoundError | Le fichier n'existe pas | open("no.txt") |
ZeroDivisionError | Division ou modulo par zéro | 1 / 0 |
AttributeError | L'objet n'a pas l'attribut | None.append(1) |
ImportError | L'importation du module échoue | import nonexistent |
OSError | L'opération OS échoue | Disque plein, permission refusée |
StopIteration | L'itérateur n'a plus d'éléments | next() sur itérateur épuisé |
RuntimeError | Erreur d'exécution générique | Fourre-tout pour les échecs divers |
Toutes ces exceptions héritent de BaseException. En pratique, vous devriez intercepter Exception ou ses sous-classes, jamais directement BaseException (qui inclut SystemExit et KeyboardInterrupt).
Lever des exceptions avec raise
Vous pouvez lever des exceptions explicitement quand votre code détecte des conditions invalides. C'est ainsi que vous imposez des préconditions et signalez des erreurs à l'appelant.
def set_age(age):
if not isinstance(age, int):
raise TypeError(f"Age must be an integer, got {type(age).__name__}")
if age < 0 or age > 150:
raise ValueError(f"Age must be between 0 and 150, got {age}")
return age
# Valid usage
print(set_age(25)) # 25
# Invalid usage
try:
set_age(-5)
except ValueError as e:
print(e) # Age must be between 0 and 150, got -5
try:
set_age("thirty")
except TypeError as e:
print(e) # Age must be an integer, got strVous pouvez également relever l'exception actuelle à l'intérieur d'un bloc except en utilisant raise sans arguments. Cela est utile quand vous voulez enregistrer une erreur puis la laisser se propager.
import logging
try:
result = 10 / 0
except ZeroDivisionError:
logging.error("Division by zero encountered")
raise # re-raises the original ZeroDivisionErrorClasses d'exceptions personnalisées
Pour les projets plus importants, définissez vos propres classes d'exceptions. Les exceptions personnalisées rendent la gestion des erreurs plus lisible et permettent aux appelants d'intercepter des modes de défaillance spécifiques.
class ValidationError(Exception):
"""Raised when input data fails validation."""
def __init__(self, field, message):
self.field = field
self.message = message
super().__init__(f"Validation failed on '{field}': {message}")
class DatabaseConnectionError(Exception):
"""Raised when the database connection fails."""
pass
# Usage
def validate_email(email):
if "@" not in email:
raise ValidationError("email", "Must contain @ symbol")
if "." not in email.split("@")[1]:
raise ValidationError("email", "Domain must contain a dot")
return email
try:
validate_email("userexample.com")
except ValidationError as e:
print(e) # Validation failed on 'email': Must contain @ symbol
print(e.field) # email
print(e.message) # Must contain @ symbolLes exceptions personnalisées doivent hériter de Exception, pas de BaseException. Regroupez les exceptions liées sous une classe de base commune pour permettre aux appelants d'intercepter des catégories larges ou étroites :
class AppError(Exception):
"""Base exception for this application."""
pass
class ConfigError(AppError):
pass
class NetworkError(AppError):
pass
# Caller can catch all app errors or specific ones
try:
raise NetworkError("Connection timed out")
except AppError as e:
print(f"Application error: {e}")Bonnes pratiques pour la gestion des exceptions Python
1. N'interceptez jamais des exceptions génériques
Un except: générique intercepte tout, y compris KeyboardInterrupt et SystemExit. Cela masque les bugs et rend le débogage cauchemardesque.
# BAD - catches everything, hides real bugs
try:
do_something()
except:
pass
# GOOD - catches specific exceptions
try:
do_something()
except ValueError as e:
logging.warning(f"Invalid value: {e}")Si vous devez intercepter un large éventail, utilisez plutôt except Exception. Cela laisse quand même KeyboardInterrupt et SystemExit se propager.
2. Gardez les blocs Try petits
Ne mettez que le code susceptible de lever l'exception à l'intérieur du bloc try. Les grands blocs try rendent difficile l'identification de la ligne qui a causé l'erreur.
# BAD - too much code in try
try:
data = load_data()
cleaned = clean_data(data)
result = analyze(cleaned)
save_results(result)
except Exception as e:
print(f"Something failed: {e}")
# GOOD - narrow try blocks
data = load_data()
cleaned = clean_data(data)
try:
result = analyze(cleaned)
except ValueError as e:
print(f"Analysis failed: {e}")
result = default_result()
save_results(result)3. Enregistrez les exceptions, ne les masquez pas
Avaler les exceptions avec pass crée des bugs invisibles. Enregistrez ou signalez toujours l'erreur.
import logging
try:
process_record(record)
except ValueError as e:
logging.error(f"Failed to process record {record['id']}: {e}")4. Utilisez des types d'exceptions spécifiques
Interceptez le type d'exception le plus spécifique possible. Cela empêche de gérer accidentellement des erreurs que vous n'avez pas anticipées.
| Approche | Intercepte | Niveau de risque |
|---|---|---|
except: | Tout y compris SystemExit | Très élevé |
except Exception: | Toutes les exceptions standard | Élevé |
except ValueError: | Uniquement ValueError | Faible |
except (ValueError, TypeError): | Deux types spécifiques | Faible |
5. Nettoyez les ressources dans Finally ou utilisez des gestionnaires de contexte
Pour les descripteurs de fichiers, les connexions de base de données et les verrous, utilisez toujours finally ou (encore mieux) une instruction with.
# Prefer context managers for resource cleanup
with open("data.txt", "r") as f:
content = f.read()
# File is automatically closed, even if an exception occursExemples concrets
Lire et analyser un fichier JSON
import json
def read_json_config(filepath):
"""Read a JSON configuration file with proper error handling."""
try:
with open(filepath, "r") as f:
config = json.load(f)
except FileNotFoundError:
print(f"Config file not found: {filepath}")
return None
except PermissionError:
print(f"No permission to read: {filepath}")
return None
except json.JSONDecodeError as e:
print(f"Invalid JSON at line {e.lineno}, column {e.colno}: {e.msg}")
return None
else:
print(f"Loaded config with keys: {list(config.keys())}")
return config
config = read_json_config("app_config.json")
if config:
db_host = config.get("database_host", "localhost")Effectuer des appels API HTTP
import urllib.request
import urllib.error
import json
def fetch_user(user_id):
"""Fetch user data from an API with retry logic."""
url = f"https://jsonplaceholder.typicode.com/users/{user_id}"
max_retries = 3
for attempt in range(1, max_retries + 1):
try:
with urllib.request.urlopen(url, timeout=5) as response:
data = json.loads(response.read().decode())
return data
except urllib.error.HTTPError as e:
if e.code == 404:
print(f"User {user_id} not found.")
return None
print(f"HTTP error {e.code} on attempt {attempt}")
except urllib.error.URLError as e:
print(f"Network error on attempt {attempt}: {e.reason}")
except json.JSONDecodeError:
print("API returned invalid JSON.")
return None
print(f"All {max_retries} attempts failed.")
return None
user = fetch_user(1)
if user:
print(f"Found user: {user['name']}")Traitement de données CSV
import csv
def process_sales_data(filepath):
"""Process a CSV file with robust error handling."""
results = []
try:
with open(filepath, "r", newline="") as f:
reader = csv.DictReader(f)
for row_num, row in enumerate(reader, start=2):
try:
amount = float(row["amount"])
quantity = int(row["quantity"])
results.append({
"product": row["product"],
"total": amount * quantity,
})
except KeyError as e:
print(f"Row {row_num}: Missing column {e}")
except ValueError as e:
print(f"Row {row_num}: Invalid number - {e}")
except FileNotFoundError:
print(f"File not found: {filepath}")
except PermissionError:
print(f"Cannot read file: {filepath}")
return resultsTry/Except vs If/Else : Quand utiliser chacun
Python suit le principe EAFP : "Easier to Ask Forgiveness than Permission" (Il est plus facile de demander pardon que la permission). Cela contraste avec l'approche LBYL : "Look Before You Leap" (Regarder avant de sauter).
# LBYL (Look Before You Leap) - using if/else
if "email" in user_data:
email = user_data["email"]
else:
email = "unknown"
# EAFP (Easier to Ask Forgiveness) - using try/except
try:
email = user_data["email"]
except KeyError:
email = "unknown"| Critère | if/else (LBYL) | try/except (EAFP) |
|---|---|---|
| Meilleur quand | La vérification est peu coûteuse et l'erreur est fréquente | L'erreur est rare ou la vérification est coûteuse |
| Performance (pas d'erreur) | Légèrement plus lent (vérification supplémentaire à chaque fois) | Légèrement plus rapide (pas de surcoût de vérification) |
| Performance (erreur survient) | Identique | Plus lent (la création d'exception a un surcoût) |
| Conditions de course | Possibles (l'état peut changer entre la vérification et l'utilisation) | Aucune (opération atomique) |
| Lisibilité | Claire pour les conditions simples | Meilleure pour les opérations pouvant échouer de multiples façons |
| Opérations fichier | if os.path.exists(path) -- le fichier pourrait être supprimé entre la vérification et l'ouverture | try: open(path) -- gère l'échec réel |
| Accès dictionnaire | if key in dict -- simple et rapide | try: dict[key] -- ou utilisez simplement dict.get(key, default) |
Utilisez try/except quand :
- Le cas d'échec est rare (les exceptions sont optimisées pour le chemin "sans erreur").
- La vérification elle-même est aussi coûteuse que l'opération (par exemple, vérifier si un fichier existe puis l'ouvrir).
- Plusieurs choses peuvent mal tourner dans une séquence d'opérations.
- Les conditions de course sont une préoccupation (surtout avec les fichiers et les ressources réseau).
Utilisez if/else quand :
- La condition est peu coûteuse à vérifier et les échecs sont fréquents.
- Vous validez l'entrée utilisateur avant le traitement.
- La logique se lit plus clairement comme un conditionnel.
Déboguer les exceptions plus rapidement avec RunCell
Quand vous travaillez dans des notebooks Jupyter, les exceptions peuvent interrompre votre flux d'analyse. Vous rencontrez un KeyError à la ligne 50 000 d'un DataFrame, ou un TypeError apparaît trois cellules en profondeur dans un pipeline. Trouver la cause racine signifie faire défiler les tracebacks et inspecter les variables manuellement.
RunCell (opens in a new tab) est un agent IA qui s'exécute directement dans Jupyter. Il lit le traceback complet, inspecte les variables dans votre portée actuelle et suggère une correction en contexte. Voici comment il aide avec la gestion des exceptions :
- Analyse du traceback. RunCell analyse la chaîne d'exceptions et identifie quelle variable ou opération a causé l'échec, même dans les appels de fonctions imbriqués.
- Suggestions de corrections. Au lieu de chercher sur Stack Overflow, RunCell génère une cellule de code corrigée que vous pouvez exécuter immédiatement. Il sait s'il faut ajouter un
try/except, corriger une conversion de type ou gérer une clé manquante. - Vérifications préventives. RunCell peut scanner votre code et signaler les opérations susceptibles de lever des exceptions -- comme accéder à des clés de dictionnaire sans
.get(), ou diviser sans vérifier le zéro -- avant que vous n'exécutiez la cellule.
Comme RunCell fonctionne dans votre environnement Jupyter existant, il a accès à vos données et variables réelles. Les suggestions qu'il fournit sont spécifiques à votre situation, pas des conseils génériques.
FAQ
Quelle est la différence entre try/except et try/catch en Python ?
Python utilise try/except, pas try/catch. La syntaxe try/catch appartient à des langages comme Java, JavaScript et C++. En Python, le mot-clé est except. La fonctionnalité est la même : vous tentez du code qui pourrait échouer et définissez un gestionnaire pour le cas d'échec.
Puis-je utiliser plusieurs blocs except en Python ?
Oui. Vous pouvez enchaîner autant de blocs except que nécessaire, chacun interceptant un type d'exception différent. Python les évalue dans l'ordre et exécute le premier bloc correspondant. Vous pouvez également intercepter plusieurs exceptions dans un seul bloc en utilisant un tuple : except (ValueError, TypeError) as e:.
Quand dois-je utiliser else avec try/except ?
Utilisez le bloc else quand vous avez du code qui ne doit s'exécuter que si le bloc try a réussi. Le principal avantage est que les exceptions levées dans le bloc else ne sont pas interceptées par les blocs except précédents, ce qui empêche de masquer accidentellement des erreurs sans rapport.
Est-ce que finally s'exécute toujours en Python ?
Oui. Le bloc finally s'exécute que le bloc try se soit terminé normalement, ait levé une exception gérée ou ait levé une exception non gérée. Il s'exécute même si le bloc try ou except contient une instruction return. Les seules exceptions sont si le processus Python est tué de l'extérieur ou si os._exit() est appelé.
Comment créer des exceptions personnalisées en Python ?
Créez une nouvelle classe qui hérite de Exception. Vous pouvez ajouter des attributs personnalisés et redéfinir la méthode __init__. Par exemple : class MyError(Exception): pass. Pour des cas plus complexes, ajoutez des champs comme des codes d'erreur ou des données de contexte. Héritez toujours de Exception, pas de BaseException.
Conclusion
Le try/except de Python est le mécanisme standard pour gérer les erreurs d'exécution. Il vous permet d'intercepter des exceptions spécifiques, d'exécuter du code de nettoyage et de maintenir la stabilité de vos programmes quand les choses tournent mal. Le patron complet -- try/except/else/finally -- couvre chaque scénario : tenter l'opération, gérer les échecs, exécuter la logique de succès et nettoyer les ressources.
Les principes clés sont simples. Interceptez des exceptions spécifiques, pas génériques. Gardez vos blocs try petits. Enregistrez ou signalez toujours les erreurs au lieu de les masquer. Utilisez finally ou les gestionnaires de contexte pour le nettoyage. Levez des exceptions significatives dans votre propre code avec des messages d'erreur clairs.
Que vous lisiez des fichiers, fassiez des appels API ou traitiez des entrées utilisateur, une gestion correcte des exceptions fait la différence entre un script qui plante à 2 heures du matin et un qui enregistre l'erreur et continue de fonctionner. Commencez par les bases -- un simple try/except autour du code qui pourrait échouer -- et évoluez vers des hiérarchies d'exceptions personnalisées à mesure que vos projets grandissent.