5 Modèles de gestion des erreurs dans Python (au-delà de l’essai à l’exception)

En ce qui concerne la gestion des erreurs, la première chose que nous apprenons habituellement est de savoir comment utiliser les blocs d’essai à l’exception. Mais est-ce vraiment suffisant car notre base de code devient plus complexe? Je ne crois pas. S’appuyer uniquement sur le TRY-Except peut conduire à un code répétitif, encombré et difficile à établir.
Dans cet article, je vais vous guider 5 modèles avancés mais pratiques de gestion des erreurs Cela peut rendre votre code plus propre, plus fiable et plus facile à déboguer. Chaque modèle est livré avec un exemple du monde réel afin que vous puissiez voir clairement où et pourquoi cela a du sens. Alors, commençons.
1. Aggrégation d’erreur pour le traitement par lots
Lors du traitement de plusieurs éléments (par exemple, dans une boucle), vous voudrez peut-être continuer le traitement même si certains éléments échouent, puis signaler toutes les erreurs à la fin. Ce modèle, appelé agrégation d’erreursévite de s’arrêter sur le premier échec. Ce modèle est excellent pour la validation du formulaire, les scénarios d’importation de données ou toute situation où vous souhaitez fournir des commentaires complets sur tous les problèmes plutôt que de s’arrêter à la première erreur.
Exemple: Traitement d’une liste des enregistrements utilisateur. Continuez même si certains échouent.
def process_user_record(record, record_number):
if not record.get("email"):
raise ValueError(f"Record #{record_number} failed: Missing email in record {record}")
# Simulate processing
print(f"Processed user #{record_number}: {record('email')}")
def process_users(records):
errors = ()
for index, record in enumerate(records, start=1):
try:
process_user_record(record, index)
except ValueError as e:
errors.append(str(e))
return errors
users = (
{"email": "qasim@example.com"},
{"email": ""},
{"email": "zeenat@example.com"},
{"email": ""}
)
errors = process_users(users)
if errors:
print("nProcessing completed with errors:")
for error in errors:
print(f"- {error}")
else:
print("All records processed successfully")
Ce code fait boucler les enregistrements et les processus utilisateur chacun individuellement. Si un enregistrement manque un e-mail, il soulève une valeur d’énergie, qui est capturée et stockée dans la liste des erreurs. Le processus se poursuit pour tous les enregistrements, et tous les échecs sont signalés à la fin sans arrêter le lot entier comme ceci:
Output:
Processed user #1: qasim@example.com
Processed user #3: zeenat@example.com
Processing completed with errors:
- Record #2 failed: Missing email in record {'email': ''}
- Record #4 failed: Missing email in record {'email': ''}
2. Modèle de gestion de contexte pour la gestion des ressources
Lorsque vous travaillez avec des ressources comme les fichiers, les connexions de base de données ou les prises de réseau, vous devez vous assurer qu’elles sont correctement ouvertes et fermées, même si une erreur se produit. Les gestionnaires de contexte, en utilisant l’instruction With avec, gérez cela automatiquement, en réduisant les chances de fuites de ressources par rapport aux blocs d’essai manuels. Ce modèle est particulièrement utile pour les opérations d’E / S ou lorsqu’ils traitent des systèmes externes.
Exemple: Disons que vous lisez un fichier CSV et que vous souhaitez vous assurer qu’il est fermé correctement, même si le traitement du fichier échoue.
import csv
def read_csv_data(file_path):
try:
with open(file_path, 'r') as file:
print(f"Inside 'with': file.closed = {file.closed}") # Should be False
reader = csv.reader(file)
for row in reader:
if len(row) < 2:
raise ValueError("Invalid row format")
print(row)
print(f"After 'with': file.closed = {file.closed}") # Should be True
except FileNotFoundError:
print(f"Error: File {file_path} not found")
print(f"In except block: file is closed? {file.closed}")
except ValueError as e:
print(f"Error: {e}")
print(f"In except block: file is closed? {file.closed}")
# Create test file
with open("data.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerows((("Name", "Age"), ("Sarwar", "30"), ("Babar"), ("Jamil", "25")))
# Run
read_csv_data("data.csv")
Ce code utilise une instruction avec (Context Manager) pour ouvrir et lire en toute sécurité le fichier. Si une ligne a moins de 2 valeurs, elle augmente un Value Enerrormais le fichier est toujours fermé automatiquement. Le file.cosé Les vérifications confirment l’état du fichier à l’intérieur et après le bloc avec le bloc, même en cas d’erreur. Exécutons le code ci-dessus pour observer ce comportement:
Output:
Inside 'with': file.closed = False
('Name', 'Age')
('Sarwar', '30')
Error: Invalid row format
In except block: file is closed? True
3. Emballage d’exception pour les erreurs contextuelles
Parfois, une exception dans une fonction de niveau inférieur ne fournit pas suffisamment de contexte sur ce qui n’a pas fonctionné dans l’application plus large. L’emballage (ou chaînage) des exceptions vous permet d’attraper une exception, d’ajouter un contexte et de ré-ranger une nouvelle exception qui inclut l’original. Il est particulièrement utile dans les applications en couches (par exemple, API ou services).
Exemple: Supposons que vous alliez récupérer les données utilisateur d’une base de données et que vous souhaitez fournir un contexte lorsqu’une erreur de base de données se produit.
class DatabaseAccessError(Exception):
"""Raised when database operations fail."""
pass
def fetch_user(user_id):
try:
# Simulate database query
raise ConnectionError("Failed to connect to database")
except ConnectionError as e:
raise DatabaseAccessError(f"Failed to fetch user {user_id}") from e
try:
fetch_user(123)
except DatabaseAccessError as e:
print(f"Error: {e}")
print(f"Caused by: {e.__cause__}")
Le ConnectionError est attrapé et enveloppé dans un DatabaseAccesserror avec un contexte supplémentaire sur l’ID utilisateur. La syntaxe From E relie l’exception d’origine, de sorte que la chaîne d’erreur complète est disponible pour le débogage. La sortie peut ressembler à ceci:
Output:
Error: Failed to fetch user 123
Caused by: Failed to connect to database
4. Réessayer la logique pour les échecs transitoires
Certaines erreurs, comme les délais d’expiration du réseau ou l’indisponibilité des services temporaires, sont transitoires et peuvent se résoudre en retrait. L’utilisation d’un motif de réessayer peut les gérer gracieusement sans encombrer votre code avec des boucles manuelles. Il automatise la récupération des défaillances temporaires.
Exemple: Réessayons un appel API feuilleté qui échoue occasionnellement en raison des erreurs de réseau simulées. Le code ci-dessous tente l’appel de l’API plusieurs fois avec un délai fixe entre les tentatives. Si l’appel réussit, il renvoie le résultat immédiatement. Si toutes les tentatives échouent, il soulève une exception à gérer par l’appelant.
import random
import time
def flaky_api_call():
# Simulate 50% chance of failure (like timeout or server error)
if random.random() < 0.5:
raise ConnectionError("Simulated network failure")
return {"status": "success", "data": (1, 2, 3)}
def fetch_data_with_retry(retries=4, delay=2):
attempt = 0
while attempt < retries:
try:
result = flaky_api_call()
print("API call succeeded:", result)
return result
except ConnectionError as e:
attempt += 1
print(f"Attempt {attempt} failed: {e}. Retrying in {delay} seconds...")
time.sleep(delay)
raise ConnectionError(f"All {retries} attempts failed.")
try:
fetch_data_with_retry()
except ConnectionError as e:
print("Final failure:", e)
Output:
Attempt 1 failed: Simulated network failure. Retrying in 2 seconds...
API call succeeded: {'status': 'success', 'data': (1, 2, 3)}
Comme vous pouvez le voir, la première tentative a échoué en raison de l’erreur de réseau simulée (qui se produit au hasard 50% du temps). La logique de réessayer a attendu 2 secondes, puis a réussi l’appel de l’API lors de la prochaine tentative.
5. Classes d’exception personnalisées pour les erreurs spécifiques au domaine
Au lieu de compter sur des exceptions génériques comme Value Enerror ou RunTimeErrorvous pouvez créer des classes d’exception personnalisées pour représenter des erreurs spécifiques dans le domaine de votre application. Cela rend la gestion des erreurs plus sémantique et plus facile à entretenir.
Exemple: Supposons un système de traitement des paiements où différents types d’échecs de paiement nécessitent une manipulation spécifique.
class PaymentError(Exception):
"""Base class for payment-related exceptions."""
pass
class InsufficientFundsError(PaymentError):
"""Raised when the account has insufficient funds."""
pass
class InvalidCardError(PaymentError):
"""Raised when the card details are invalid."""
pass
def process_payment(amount, card_details):
try:
if amount > 1000:
raise InsufficientFundsError("Not enough funds for this transaction")
if not card_details.get("valid"):
raise InvalidCardError("Invalid card details provided")
print("Payment processed successfully")
except InsufficientFundsError as e:
print(f"Payment failed: {e}")
# Notify user to top up account
except InvalidCardError as e:
print(f"Payment failed: {e}")
# Prompt user to re-enter card details
except Exception as e:
print(f"Unexpected error: {e}")
# Log for debugging
process_payment(1500, {"valid": False})
Des exceptions personnalisées (insuffisantefundDerror, invalidCarrError) héritent d’une classe de base de paiement de base, vous permettant de gérer les problèmes de paiement spécifiques différemment tout en attrapant des erreurs inattendues avec un bloc d’exception générique. Par exemple, dans le appel process_payment (1500, {« valide »: false})le premier chèque se déclenche car le montant (1500) dépasse 1000, il soulève donc insuffisant la perpérance. Cette exception est capturée dans le blocage correspondant, l’impression: l’impression:
Output:
Payment failed: Not enough funds for this transaction
Conclusion
C’est ça. Dans cet article, nous avons exploré 5 modèles de gestion des erreurs pratiques:
- Aggrégation d’erreur: Traiter tous les éléments, collecter des erreurs et les signaler ensemble
- Gestionnaire de contexte: Gérer en toute sécurité les ressources comme les fichiers avec des blocs
- Emballage des exceptions: Ajouter le contexte en attrapant et en réévaluant les exceptions
- Réessayez la logique: Rerimer automatiquement les erreurs transitoires comme les défaillances du réseau
- Exceptions personnalisées: Créer des classes d’erreur spécifiques pour une manipulation plus claire
Essayez ces modèles dans votre prochain projet. Avec un peu de pratique, vous trouverez votre code plus facile à maintenir et votre traitement des erreurs beaucoup plus efficace.
Kanwal Mehreen Kanwal est ingénieur d’apprentissage automatique et écrivain technique avec une profonde passion pour la science des données et l’intersection de l’IA avec la médecine. Elle a co-écrit l’ebook « Maximiser la productivité avec Chatgpt ». En tant que Google Generation Scholar 2022 pour APAC, elle défend la diversité et l’excellence académique. Elle est également reconnue comme une diversité de Teradata dans Tech Scholar, le boursier de recherche Mitacs Globalink et le savant de Harvard WECODE. Kanwal est un ardent défenseur du changement, après avoir fondé des femmes pour autonomiser les femmes dans les champs STEM.