[OK] Fichier: python_cheats/cheatsheets/exceptions.txt


[OK] CHEATSHEET COMPLET - EXCEPTIONS PYTHON


[OK] 1. TRY/EXCEPT BASIQUE
[OK] Permet de capturer et gérer les erreurs sans arrêter le programme
try:
    result = 10 / 0  # Cette opération va générer une erreur
except ZeroDivisionError:
    print("Cannot divide by zero")  # Message personnalisé au lieu d'un crash


[OK] 2. MULTIPLE EXCEPTIONS
[OK] On peut gérer différents types d'erreurs avec plusieurs blocs except
try:
    data = {'name': 'John'}
    age = int(data['age'])  # Peut générer KeyError ou ValueError
except ValueError:
    print("ValueError occurred - impossible de convertir en entier")
except TypeError:
    print("TypeError occurred - type de données incorrect")
except (KeyError, IndexError):  # Grouper plusieurs exceptions
    print("KeyError or IndexError - clé ou index manquant")


[OK] 3. CAPTURER L'EXCEPTION DANS UNE VARIABLE
[OK] Le mot-clé 'as' permet de récupérer les détails de l'erreur
try:
    result = int("not a number")
except ValueError as e:
    print(f"Error: {e}")  # Affiche le message d'erreur exact
    print(f"Type: {type(e)}")  # Affiche le type d'exception


[OK] 4. ELSE ET FINALLY
[OK] else: exécuté seulement si AUCUNE exception ne s'est produite
# finally: exécuté TOUJOURS, qu'il y ait erreur ou non (idéal pour le nettoyage)
try:
    file = open('file.txt', 'r')
except FileNotFoundError:
    print("File not found")
else:
    # Ce bloc s'exécute uniquement si le fichier a été ouvert avec succès
    content = file.read()
    print(f"File content: {content}")
finally:
    # Ce bloc s'exécute toujours, même en cas d'erreur
    # Parfait pour fermer des ressources (fichiers, connexions DB, etc.)
    if 'file' in locals():
        file.close()
        print("File closed properly")


[OK] 5. EXCEPTION GÉNÉRIQUE
[OK] Exception est la classe parent de toutes les exceptions
# À utiliser avec précaution - préférer des exceptions spécifiques
try:
    risky_operation()
except Exception as e:
    print(f"An error occurred: {e}")
    print(f"Exception type: {type(e).__name__}")


[OK] 6. RAISE - LEVER UNE EXCEPTION
[OK] Permet de déclencher volontairement une exception
def validate_age(age):
    """Valide qu'un âge est dans une plage raisonnable"""
    if age < 0:
        # Lever une exception si la condition n'est pas respectée
        raise ValueError("Age cannot be negative")
    if age > 150:
        raise ValueError("Age is unrealistic")
    return age

# Utilisation
try:
    validate_age(-5)
except ValueError as e:
    print(f"Validation error: {e}")


[OK] 7. RE-RAISE - RELANCER UNE EXCEPTION
[OK] Permet de logger une erreur puis de la propager
def process_critical_data(data):
    try:
        result = perform_operation(data)
    except ValueError as e:
        # On log l'erreur pour la traçabilité
        print(f"Logging error: {e}")
        # On relance l'exception pour que le code appelant puisse aussi la gérer
        raise  # Re-lance la même exception


[OK] 8. CUSTOM EXCEPTIONS - EXCEPTIONS PERSONNALISÉES
[OK] Créer ses propres exceptions pour des cas métier spécifiques

# Exception simple
class InvalidEmailError(Exception):
    """Levée quand un email est invalide"""
    pass

# Exception avec attributs personnalisés
class DatabaseError(Exception):
    """Erreur lors d'opérations sur la base de données"""
    def __init__(self, message, error_code):
        super().__init__(message)  # Appelle le constructeur parent
        self.error_code = error_code  # Ajoute un attribut personnalisé
    
    def __str__(self):
        return f"[Error {self.error_code}] {self.args[0]}"

# Utilisation
def validate_email(email):
    if '@' not in email:
        raise InvalidEmailError(f"Invalid email format: {email}")
    return email

try:
    validate_email("invalid-email")
except InvalidEmailError as e:
    print(f"Email validation failed: {e}")


[OK] 9. HIÉRARCHIE D'EXCEPTIONS
[OK] Créer une hiérarchie permet de capturer des groupes d'exceptions
class AppError(Exception):
    """Exception de base pour toute l'application"""
    pass

class ValidationError(AppError):
    """Erreur de validation des données"""
    pass

class DatabaseError(AppError):
    """Erreur d'accès à la base de données"""
    pass

class AuthenticationError(AppError):
    """Erreur d'authentification"""
    pass

# Capturer toutes les exceptions de l'app en une fois
try:
    raise ValidationError("Invalid input")
except AppError as e:  # Capture ValidationError, DatabaseError, etc.
    print(f"Application error: {e}")
    print(f"Error category: {type(e).__name__}")


[OK] 10. ASSERT - ASSERTIONS
[OK] Vérifications de développement (peuvent être désactivées en production)
def divide(a, b):
    # Assert vérifie une condition et lève AssertionError si fausse
    assert b != 0, "Divisor cannot be zero"
    assert isinstance(a, (int, float)), "a must be a number"
    return a / b

# Note: Les assertions peuvent être désactivées avec python -O script.py
# Ne JAMAIS utiliser assert pour la validation en production
# Utiliser des if/raise à la place


[OK] 11. CONTEXT MANAGER AVEC EXCEPTIONS
[OK] Gestion automatique des ressources avec protocole __enter__/__exit__
class Transaction:
    """Simule une transaction de base de données"""
    
    def __enter__(self):
        """Appelé au début du bloc with"""
        print("Begin transaction")
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """
        Appelé à la fin du bloc with, même en cas d'erreur
        exc_type: type de l'exception (None si pas d'erreur)
        exc_val: valeur de l'exception
        exc_tb: traceback de l'exception
        """
        if exc_type is None:
            print("Commit transaction - Success")
        else:
            print(f"Rollback due to {exc_type.__name__}: {exc_val}")
        
        return False  # False = propage l'exception, True = supprime

# Utilisation
with Transaction():
    print("Executing database operations")
    # Si une erreur se produit ici, __exit__ est quand même appelé


[OK] 12. EXCEPTIONS BUILT-IN COURANTES

"""
Exception               # Classe de base de toutes les exceptions
AttributeError          # Attribut ou méthode inexistant sur un objet
EOFError                # input() atteint la fin du fichier
FileNotFoundError       # Fichier ou répertoire introuvable
ImportError             # Échec d'importation d'un module
IndexError              # Index de liste/tuple hors limites
KeyError                # Clé inexistante dans un dictionnaire
KeyboardInterrupt       # Utilisateur appuie sur Ctrl+C
MemoryError             # Mémoire insuffisante
NameError               # Variable ou fonction non définie
OSError                 # Erreur liée au système d'exploitation
RuntimeError            # Erreur générique d'exécution
StopIteration           # Fin d'itération (utilisé par les itérateurs)
SyntaxError             # Erreur de syntaxe Python
TypeError               # Opération sur un type incorrect
ValueError              # Valeur du bon type mais incorrecte
ZeroDivisionError       # Division ou modulo par zéro
"""

# Exemples d'utilisation
try:
    my_list = [1, 2, 3]
    print(my_list[10])  # IndexError
except IndexError:
    print("Index out of range")

try:
    my_dict = {'name': 'John'}
    print(my_dict['age'])  # KeyError
except KeyError as e:
    print(f"Missing key: {e}")


[OK] 13. TRACEBACK - TRAÇAGE DES ERREURS

import traceback

# Afficher la stack trace complète
try:
    def function_a():
        function_b()
    
    def function_b():
        function_c()
    
    def function_c():
        1 / 0
    
    function_a()
except Exception:
    # Affiche le chemin complet de l'erreur
    traceback.print_exc()
    
    # Ou capturer dans une string pour logging
    tb_str = traceback.format_exc()
    print("\n--- Captured traceback ---")
    print(tb_str)


[OK] 14. LOGGING D'EXCEPTIONS

import logging

# Configuration du logging
logging.basicConfig(
    level=logging.ERROR,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

try:
    risky_operation()
except Exception as e:
    # exc_info=True inclut automatiquement le traceback
    logging.error("An error occurred during operation", exc_info=True)
    # Alternative: logging.exception("Message") fait la même chose


[OK] 15. WARNINGS - AVERTISSEMENTS

import warnings

# Émettre un warning (n'arrête pas l'exécution)
def deprecated_function():
    """Cette fonction est obsolète"""
    warnings.warn(
        "This function is deprecated, use new_function() instead",
        DeprecationWarning,
        stacklevel=2  # Indique où le warning s'est produit
    )
    return "old behavior"

# Filtrer les warnings
warnings.filterwarnings('ignore', category=DeprecationWarning)
warnings.filterwarnings('error', category=UserWarning)  # Transforme en exception

# Types de warnings courants
"""
DeprecationWarning      # Fonctionnalité obsolète
FutureWarning           # Changement futur du comportement
UserWarning             # Warning générique pour l'utilisateur
RuntimeWarning          # Warning d'exécution (ex: division par zéro en numpy)
SyntaxWarning           # Syntaxe douteuse mais valide
"""


[OK] 16. EXCEPTION CHAINING - CHAÎNAGE D'EXCEPTIONS
[OK] Permet de montrer la cause d'une exception

# Chaînage explicite avec 'from'
try:
    data = {'name': 'John'}
    age = int(data['age'])  # KeyError
except KeyError as e:
    # La nouvelle exception garde la trace de l'ancienne
    raise ValueError("Unable to process user data") from e

# Supprimer le contexte avec 'from None'
try:
    problematic_operation()
except Exception:
    # Cache l'exception originale, ne montre que la nouvelle
    raise RuntimeError("Operation failed") from None


[OK] 17. EXEMPLES PRATIQUES COMPLETS


# Exemple 1: Validation robuste de données
def process_user_data(data):
    """
    Traite les données utilisateur avec validation complète
    
    Raises:
        TypeError: Si data n'est pas un dictionnaire
        KeyError: Si un champ requis est manquant
        ValueError: Si les données sont invalides
    """
    # Validation du type
    if not isinstance(data, dict):
        raise TypeError(f"Data must be a dictionary, got {type(data).__name__}")
    
    # Validation des champs requis
    required_fields = ['name', 'email', 'age']
    for field in required_fields:
        if field not in data:
            raise KeyError(f"Missing required field: {field}")
    
    # Validation et conversion des données
    try:
        age = int(data['age'])
        if age < 0 or age > 150:
            raise ValueError(f"Age {age} is out of valid range (0-150)")
    except (ValueError, TypeError) as e:
        raise ValueError(f"Invalid age value: {data['age']}") from e
    
    # Validation de l'email
    if '@' not in data['email']:
        raise ValueError(f"Invalid email format: {data['email']}")
    
    return {
        'name': data['name'].strip(),
        'email': data['email'].lower().strip(),
        'age': age
    }

# Utilisation avec gestion d'erreurs complète
try:
    user = process_user_data({'name': 'John', 'email': 'john@example.com', 'age': '25'})
    print(f"User processed: {user}")
except (TypeError, KeyError, ValueError) as e:
    print(f"Data validation failed: {e}")


# Exemple 2: Retry logic avec backoff exponentiel
from time import sleep

def fetch_with_retry(url, retries=3, backoff_factor=2):
    """
    Tente de récupérer une ressource avec réessais automatiques
    
    Args:
        url: URL à récupérer
        retries: Nombre de tentatives
        backoff_factor: Facteur multiplicateur du délai entre tentatives
    
    Returns:
        Contenu récupéré
    
    Raises:
        ConnectionError: Si toutes les tentatives échouent
    """
    for attempt in range(retries):
        try:
            print(f"Attempt {attempt + 1}/{retries}")
            return fetch(url)  # Fonction qui peut échouer
        
        except ConnectionError as e:
            # Si c'est la dernière tentative, on relance l'exception
            if attempt == retries - 1:
                raise ConnectionError(f"Failed after {retries} attempts") from e
            
            # Sinon, on attend avant de réessayer
            wait_time = backoff_factor ** attempt
            print(f"Connection failed, retrying in {wait_time}s...")
            sleep(wait_time)


# Exemple 3: Gestionnaire de contexte personnalisé
class DatabaseConnection:
    """Gère automatiquement l'ouverture et la fermeture d'une connexion DB"""
    
    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.connection = None
    
    def __enter__(self):
        """Ouvre la connexion"""
        print(f"Connecting to {self.host}:{self.port}")
        self.connection = create_connection(self.host, self.port)
        return self.connection
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Ferme la connexion, même en cas d'erreur"""
        if self.connection:
            if exc_type is None:
                print("Committing transaction")
                self.connection.commit()
            else:
                print(f"Rolling back due to {exc_type.__name__}")
                self.connection.rollback()
            
            self.connection.close()
            print("Connection closed")
        
        return False  # Propage l'exception

# Utilisation
try:
    with DatabaseConnection('localhost', 5432) as conn:
        conn.execute("INSERT INTO users VALUES (...)")
except Exception as e:
    print(f"Database operation failed: {e}")


[OK] 18. BONNES PRATIQUES


# [OK] BON: Exceptions spécifiques avant génériques
try:
    operation()
except ValueError:  # Plus spécifique en premier
    handle_value_error()
except TypeError:
    handle_type_error()
except Exception:  # Plus générique en dernier
    handle_generic_error()

# [X] MAUVAIS: Capturer tout sans discrimination
try:
    operation()
except:  # N'utilisez JAMAIS except tout seul
    pass  # Et ne jamais ignorer silencieusement

# [OK] BON: Capturer seulement ce qu'on peut gérer
try:
    result = int(user_input)
except ValueError:
    print("Please enter a valid number")
    result = 0

# [OK] BON: Documenter les exceptions dans les docstrings
def calculate_discount(price, percentage):
    """
    Calcule le prix après remise
    
    Args:
        price: Prix original (float)
        percentage: Pourcentage de remise (0-100)
    
    Returns:
        Prix après remise
    
    Raises:
        ValueError: Si price est négatif ou percentage hors limites
        TypeError: Si les arguments ne sont pas des nombres
    """
    if not isinstance(price, (int, float)) or not isinstance(percentage, (int, float)):
        raise TypeError("Arguments must be numbers")
    
    if price < 0:
        raise ValueError("Price cannot be negative")
    
    if not 0 <= percentage <= 100:
        raise ValueError("Percentage must be between 0 and 100")
    
    return price * (1 - percentage / 100)

# [OK] BON: Créer des exceptions métier spécifiques
class OrderError(Exception):
    """Exception de base pour les erreurs de commande"""
    pass

class InsufficientStockError(OrderError):
    """Stock insuffisant pour la commande"""
    def __init__(self, product, requested, available):
        self.product = product
        self.requested = requested
        self.available = available
        super().__init__(
            f"Insufficient stock for {product}: "
            f"requested {requested}, available {available}"
        )

# [OK] BON: Ne pas cacher les exceptions importantes
try:
    critical_operation()
except KeyboardInterrupt:
    # Ne jamais ignorer KeyboardInterrupt
    raise
except Exception as e:
    # Logger et gérer les autres
    log_error(e)


[OK] 19. ANTI-PATTERNS À ÉVITER


# [X] Exception trop large masquant des bugs
try:
    everything()
except Exception:
    pass  # Cache tous les bugs potentiels

# [X] Utiliser les exceptions pour le flow normal
def get_user(user_id):
    try:
        return users[user_id]
    except KeyError:
        return None  # Utilisez users.get(user_id) à la place

# [X] Créer des exceptions trop génériques
raise Exception("Something went wrong")  # Peu informatif

# [OK] À la place, être spécifique
raise ValueError(f"Invalid user_id: {user_id}")


[OK] 20. RÉSUMÉ DES MOTS-CLÉS

"""
try:        Bloc de code qui peut générer une exception
except:     Capture et gère une exception
else:       Exécuté si aucune exception n'est levée
finally:    Exécuté toujours, qu'il y ait exception ou non
raise:      Lève une exception manuellement
from:       Chaîne une exception à une autre
as:         Capture l'exception dans une variable
assert:     Vérifie une condition (mode debug uniquement)
with:       Utilise un context manager (gestion automatique des ressources)
"""