Programmation · Débogage

Débogage

Lire un traceback, choisir la bonne stratégie, maîtriser pdb et le débogueur VS Code, reconnaître les erreurs fréquentes en Python, JS et C — et développer l'instinct du débogueur.

État d'esprit du débogueur

🧠

Un bug n'est pas une catastrophe — c'est un écart entre ce que le programme fait et ce que tu penses qu'il fait. La plupart des bugs existent parce que le développeur avait une hypothèse incorrecte sur le comportement du code. Le débogage, c'est retrouver cette hypothèse fausse.

🔬
Penser comme un scientifique
Formuler une hypothèse, concevoir un test pour la vérifier, observer le résultat, mettre à jour l'hypothèse. Pas d'intuition pure.
🎯
Reproduire avant de corriger
Un bug qu'on ne peut pas reproduire à la demande ne peut pas être corrigé de façon fiable. Trouver les conditions exactes de reproduction est la moitié du travail.
🔪
Isoler par dichotomie
Diviser le code suspect en deux, tester le milieu. Le bug est dans la moitié qui échoue. Répéter jusqu'à trouver la ligne exacte.
📝
Changer une seule chose à la fois
Modifier plusieurs choses simultanément rend impossible de savoir ce qui a résolu (ou empiré) le problème.
🚫

Les pièges classiques du débogage :

Mauvaise habitudePourquoi ça ne marche pas
Modifier le code au hasard jusqu'à ce que ça marcheCrée de nouveaux bugs cachés, ne comprend pas la cause
Lire le code en cherchant l'erreur "à l'œil"Le cerveau voit ce qu'il veut voir, pas ce qui est écrit
Assumer que le bug est dans une bibliothèque externeDans 99% des cas, le bug est dans ton code
Déboguer mentalement sans exécuter de testsLes intuitions sont souvent fausses sur le comportement réel
Corriger le symptôme sans trouver la causeLe bug réapparaîtra sous une autre forme
🦆

Rubber Duck Debugging — expliquer son code à voix haute, ligne par ligne, à un canard en plastique (ou un collègue, ou soi-même). Le simple fait de verbaliser force à articuler les hypothèses implicites, et c'est souvent là que l'erreur saute aux yeux avant même de finir la phrase.

Lire un traceback

Un traceback Python se lit de bas en haut : la dernière ligne est l'erreur, les lignes au-dessus sont la pile d'appels qui a mené à l'erreur.

Traceback (most recent call last): File "main.py", line 18, in <module> resultat = calculer_moyenne(notes) File "main.py", line 12, in calculer_moyenne return somme / len(liste) ↑↑↑↑↑ la ligne exacte qui a planté ZeroDivisionError: division by zero
① Type d'erreurZeroDivisionError : on divise par zéro. La liste passée à la fonction est vide.
② Messagedivision by zero : confirme la cause directe.
③ Ligne fautiveline 12, in calculer_moyenne → c'est len(liste) qui vaut 0.
④ Appelantline 18 : c'est main.py qui a appelé la fonction avec une liste vide. La vraie cause est peut-être là.

Tracebacks JS (Node.js / navigateur) :

TypeError: Cannot read properties of undefined (reading 'name') at getUser (app.js:24:18) at processRequest (app.js:11:20) at Object.<anonymous> (app.js:5:1)
JS : lire de haut en bas — contrairement à Python, la première ligne est l'erreur, et la pile d'appels suit dans l'ordre chronologique inverse. La ligne du crash est app.js:24 dans getUser. user est undefined à ce moment-là.

Erreur de compilation C :

main.c:8:5: error: use of undeclared identifier 'comptuer' comptuer++; // typo : "compteur" mal orthographié ^~~~~~~ 1 error generated.
C : format fichier:ligne:colonne — le compilateur indique précisément la position. La flèche ^ pointe sur le token fautif. Ici une faute de frappe dans le nom de variable.
💡

Quand il y a plusieurs erreurs (C, Java…) : corriger toujours la première en premier. Les erreurs suivantes sont souvent des effets de cascade de la première.

Méthode scientifique de débogage

1
Observer — qu'est-ce qui se passe exactement ?
Lire l'erreur complète sans sauter aux conclusions. Copier le traceback exact. Identifier le type d'erreur, le fichier, la ligne.
"ZeroDivisionError à la ligne 12, dans calculer_moyenne"
2
Reproduire — obtenir le bug à la demande
Trouver le cas minimal qui déclenche l'erreur. Simplifier les données d'entrée au maximum. Un bug reproductible est à moitié résolu.
"Le bug se produit quand on appelle calculer_moyenne([])"
3
Émettre une hypothèse
Formuler une explication précise et testable. Éviter "c'est bizarre" — chaque bug a une cause déterministe.
"Hypothèse : la liste est vide quand elle arrive dans la fonction, len() renvoie 0"
4
Tester l'hypothèse
Ajouter un print / breakpoint pour vérifier la valeur incriminée. Pas plus d'une hypothèse testée à la fois.
print(f"liste reçue : {liste}, len : {len(liste)}")
5
Corriger la cause, pas le symptôme
Comprendre pourquoi le bug est apparu, pas juste le faire disparaître. Souvent la vraie cause est en amont de la ligne qui plante.
Corriger : valider que la liste n'est pas vide avant l'appel, pas juste ajouter if len == 0: return 0
6
Vérifier que la correction n'a pas cassé autre chose
Exécuter les tests existants. Tester les cas limites autour de la correction. Un fix qui crée un nouveau bug est un net négatif.
Tester avec [], [0], [0, 0], [valeur_max], None…
Isolation par dichotomie (binary search)
# Le bug est quelque part dans 100 lignes.
# Stratégie : commenter la moitié du code
# et tester si le bug est encore là.

def traiter_donnees(data):
    data = nettoyer(data)         # ← est-ce que le bug est ici ?
    # --- POINT DE TEST DICHOTOMIE ---
    print("data après nettoyage:", data)  # vérifier ici
    # ---------------------------------
    data = transformer(data)      # ou ici ?
    data = calculer_stats(data)   # ou ici ?
    return exporter(data)         # ou ici ?

# Si le print montre des données correctes :
# → le bug est APRÈS (dans transformer, calculer_stats, exporter)
# Si les données sont déjà incorrectes :
# → le bug est DANS nettoyer ou dans les données d'entrée
# Recommencer avec la moitié qui contient le bug.
Cas minimal de reproduction (MRE)
# Transformer un bug complexe en exemple minimal

# ✗ Trop complexe pour déboguer
app = Application()
app.charger_config("config.json")
app.connecter_base()
app.lancer_serveur()
# ... 200 lignes plus tard : KeyError quelque part

# ✓ Cas minimal : isoler le dictionnaire incriminé
d = {"utilisateurs": [{"nom": "Alice"}]}
# Reproduit l'erreur sans l'application entière :
print(d["utilisateurs"][0]["email"])  # → KeyError: 'email'

# Maintenant la cause est évidente :
# la clé "email" n'existe pas dans le dict utilisateur.

print & logging

print() — débogage rapide
# ✗ Print inutile — ne donne aucune info
print(x)

# ✓ Print informatif — type, valeur, contexte
print(f"[DEBUG] x = {x!r} (type: {type(x).__name__})")
# !r utilise repr() → montre None vs "None", '' vs " ", etc.

# ✓ Inspecter un dictionnaire ou une liste
import json
print(json.dumps(data, indent=2, default=str))  # lisible

# ✓ Tracer le flux d'exécution
def ma_fonction(x, y):
    print(f"→ ma_fonction appelée avec x={x!r}, y={y!r}")
    result = x + y
    print(f"← ma_fonction retourne {result!r}")
    return result

# ✓ Afficher la pile d'appels depuis n'importe où
import traceback
traceback.print_stack()  # qui a appelé cette fonction ?

# ✓ Breakpoint express (Python 3.7+)
breakpoint()  # équivalent à import pdb; pdb.set_trace()
              # mais peut être désactivé avec PYTHONBREAKPOINT=0
logging — débogage en production
import logging

# Configuration de base (à mettre dans le main)
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    datefmt="%H:%M:%S",
    # filename="app.log"  ← pour écrire dans un fichier
)
logger = logging.getLogger(__name__)

# 5 niveaux — du plus détaillé au plus critique
logger.debug(f"Valeur intermédiaire : {x}")    # dev uniquement
logger.info("Traitement démarré")              # flux normal
logger.warning(f"Liste vide reçue pour {id}") # suspect
logger.error("Connexion DB échouée")           # erreur récupérable
logger.critical("Crash imminent")              # fatal

# En production : passer à WARNING pour ne voir que les problèmes
logging.basicConfig(level=logging.WARNING)

# Avantage sur print : on peut filtrer, horodater,
# rediriger vers fichier/Sentry/Datadog sans changer le code
🧹

Les print() de débogage doivent être retirés avant de committer. Utiliser logging.debug() est meilleur : il peut être désactivé sans toucher au code, et ne pollue pas la sortie en production.

assert & préconditions

assert — valider ses hypothèses dans le code
# assert condition, "message si faux"
# Lance AssertionError si la condition est False

def calculer_moyenne(notes: list) -> float:
    # Préconditions : rendre les hypothèses explicites
    assert isinstance(notes, list), f"notes doit être une liste, reçu {type(notes)}"
    assert len(notes) > 0,          "La liste ne doit pas être vide"
    assert all(0 <= n <= 20 for n in notes), "Toutes les notes doivent être entre 0 et 20"

    # Traitement
    moyenne = sum(notes) / len(notes)

    # Postcondition : vérifier le résultat
    assert 0 <= moyenne <= 20, f"Résultat incohérent : {moyenne}"
    return moyenne

# Utilisation
calculer_moyenne(["a", "b"])  # → AssertionError: notes doit être une liste...
calculer_moyenne([])          # → AssertionError: La liste ne doit pas être vide
calculer_moyenne([10, 14])   # → 12.0

# ⚠️ assert est désactivé avec python -O (optimized)
# → pour les vraies validations d'entrée, utiliser ValueError/TypeError
if not notes:
    raise ValueError("La liste de notes ne peut pas être vide")
Invariants dans les boucles
# Vérifier qu'un invariant est maintenu à chaque itération
def tri_bulles(lst):
    n = len(lst)
    for i in range(n):
        for j in range(0, n - i - 1):
            if lst[j] > lst[j + 1]:
                lst[j], lst[j + 1] = lst[j + 1], lst[j]
        # Invariant : les i derniers éléments sont à leur place
        assert lst[n-i-1:] == sorted(lst)[n-i-1:], \
            f"Invariant violé à l'itération {i}: {lst}"
    return lst
OutilQuand l'utiliser
assertVérifier des hypothèses développeur en dev/test
raise ValueErrorValider des entrées utilisateur/API en production
raise TypeErrorMauvais type passé à une fonction
logging.warningSituation anormale mais récupérable
Test unitaireVérification reproductible et automatisée

Débogueur Python — pdb

Démarrer pdb
# Méthode 1 — breakpoint() dans le code (Python 3.7+)
def ma_fonction(x):
    breakpoint()   # le programme s'arrête ici
    return x * 2

# Méthode 2 — depuis la ligne de commande
python -m pdb mon_script.py

# Méthode 3 — après une exception (post-mortem)
import pdb
try:
    code_qui_plante()
except:
    pdb.post_mortem()  # inspecter l'état au moment du crash

# L'invite pdb ressemble à ceci :
# > main.py(8)ma_fonction()
# -> return x * 2
# (Pdb) _
Commandes essentielles
# Navigation
n         # next — ligne suivante (sans entrer)
s         # step — entrer dans la fonction
r         # return — sortir de la fonction
c         # continue — jusqu'au prochain breakpoint
q         # quit — quitter pdb

# Inspection
p x       # print(x) — afficher une variable
pp x      # pretty-print (dicts, listes...)
l         # list — afficher le code autour
ll        # longlist — toute la fonction
w         # where — pile d'appels
a         # args — arguments de la fonction

# Breakpoints
b 12      # breakpoint à la ligne 12
b fn      # breakpoint au début de fn
b         # lister les breakpoints
cl 1      # clear breakpoint #1
b 12, x>5 # breakpoint conditionnel !

# Expressions
!x = 42   # modifier x en cours d'exécution
# Toute expression Python est valide dans pdb
Session pdb commentée
# Code
def traiter(items):
    total = 0
    breakpoint()
    for item in items:
        total += item["valeur"]
    return total

# Session pdb
# > traiter(items)
# (Pdb) p items
# [{'valeur': 10}, {'val': 20}]   ← ici ! "val" pas "valeur"
# (Pdb) p items[1]
# {'val': 20}                     ← clé différente !
# (Pdb) n                         ← avancer
# (Pdb) n
# KeyError: 'valeur'              ← bug trouvé !
# (Pdb) q
🚀

ipdb — version améliorée de pdb avec coloration syntaxique et autocomplétion. Installer avec pip install ipdb. S'utilise de la même façon : import ipdb; ipdb.set_trace(). Fortement recommandé pour une meilleure expérience en terminal.

⌨️

Dans pdb, appuyer sur Entrée sans rien taper répète la dernière commande. Pratique pour avancer rapidement ligne par ligne avec n répété.

VS Code Debugger

launch.json — configuration Python
// .vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python : fichier courant",
      "type": "debugpy",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal"
    },
    {
      "name": "Python : main.py avec args",
      "type": "debugpy",
      "request": "launch",
      "program": "${workspaceFolder}/main.py",
      "args": ["--input", "data.csv"],
      "env": { "DEBUG": "1" }
    },
    {
      "name": "Python : tests pytest",
      "type": "debugpy",
      "request": "launch",
      "module": "pytest",
      "args": ["-v", "tests/"]
    }
  ]
}
ActionRaccourciÉquivalent pdb
Lancer le débogueurF5python -m pdb
Ligne suivante (sans entrer)F10n
Entrer dans la fonctionF11s
Sortir de la fonctionShift+F11r
Continuer jusqu'au BPF5c
Poser/retirer un BPF9b / cl
ArrêterShift+F5q
🔴

Breakpoints conditionnels dans VS Code : clic droit sur un breakpoint existant → "Edit Breakpoint…" → entrer une condition Python ex: i == 5 ou len(data) == 0. Le programme ne s'arrêtera que quand la condition est vraie. Idéal pour les boucles avec des milliers d'itérations.

👁️

Watch expressions — dans le panneau "Watch" du débogueur VS Code, ajouter des expressions à surveiller (len(data), user.is_admin…). Elles se mettent à jour automatiquement à chaque pas.

Erreurs Python

Python NameError Variable non définie
Cause : utilisation d'une variable avant qu'elle soit définie, ou faute de frappe dans son nom. Fréquent aussi avec les variables hors de leur portée (définie dans un if qui n'a pas été exécuté).
Exemples
# ✗ Faute de frappe
compteur = 0
compteur += 1
print(comteur)  # NameError: name 'comteur' is not defined

# ✗ Variable définie conditionnellement
if condition:
    resultat = calculer()
print(resultat)  # NameError si condition était False

# ✓ Initialiser avant le if
resultat = None
if condition:
    resultat = calculer()
print(resultat)  # None si condition False, valeur sinon
Python TypeError Opération sur un mauvais type
Cause : tentative d'opération incompatible avec le type de la variable. Souvent une chaîne là où un nombre est attendu, ou None là où un objet est attendu.
Exemples
# ✗ str + int sans conversion
age = input("Âge : ")      # input() renvoie TOUJOURS une str
print("Dans 10 ans : " + age + 10)  # TypeError: can only concatenate str

# ✓ Convertir explicitement
age = int(input("Âge : "))
print(f"Dans 10 ans : {age + 10}")

# ✗ Appeler None comme une fonction
def get_user():
    user = chercher_user()
    # oubli du return !

user = get_user()
user.afficher()  # TypeError: 'NoneType' object is not callable

# Diagnostic rapide : toujours vérifier le type
print(f"type(x) = {type(x)}, valeur = {x!r}")
Python IndexError / KeyError Accès hors limites
Cause : accès à un index qui n'existe pas dans une liste, ou à une clé absente dans un dictionnaire.
Exemples et corrections
# ✗ IndexError — liste vide ou index trop grand
lst = [1, 2, 3]
print(lst[5])    # IndexError: list index out of range

# ✓ Vérifier avant d'accéder
if len(lst) > 5:
    print(lst[5])

# ✗ KeyError — clé absente
d = {"nom": "Alice"}
print(d["email"])  # KeyError: 'email'

# ✓ Option 1 : .get() avec valeur par défaut
print(d.get("email", "non renseigné"))

# ✓ Option 2 : vérifier avec `in`
if "email" in d:
    print(d["email"])
Python AttributeError Attribut inexistant sur l'objet
Cause : appel d'une méthode ou accès à un attribut qui n'existe pas sur le type réel de l'objet. Souvent causé par un None inattendu ou un mauvais type.
Exemples
# ✗ Méthode inexistante sur le type réel
x = 42
x.upper()  # AttributeError: 'int' object has no attribute 'upper'
# → x est un int, pas une str. D'où vient ce int ?

# ✗ Objet None — très fréquent !
user = trouver_user("alice")   # peut retourner None
print(user.nom)  # AttributeError: 'NoneType' object has no attribute 'nom'

# ✓ Vérifier None avant d'accéder
user = trouver_user("alice")
if user is not None:
    print(user.nom)
# ou avec le walrus operator (Python 3.8+)
if user := trouver_user("alice"):
    print(user.nom)
Python IndentationError Indentation incorrecte
Cause : mélange d'espaces et de tabulations, ou mauvais niveau d'indentation. Python est strict sur l'indentation — elle définit les blocs.
Diagnostic et correction
# ✗ Mélange tabs et espaces (invisible à l'œil !)
def f():
    x = 1   # 4 espaces
	y = 2   # 1 tabulation → IndentationError

# ✓ VS Code : afficher les caractères invisibles
# View → Render Whitespace (ou Ctrl+Shift+P → "Toggle Render Whitespace")
# Configurer : "Editor: Detect Indentation" → off, "Editor: Insert Spaces" → on

# ✓ Convertir les tabs en espaces dans VS Code
# Ctrl+Shift+P → "Convert Indentation to Spaces"

# ✓ En ligne de commande
python -tt mon_fichier.py  # détecte les TabError

Erreurs JavaScript

JS TypeError: Cannot read properties of undefined Accès à une propriété de undefined/null
Cause : l'objet est undefined ou null à ce moment-là. Très fréquent avec les données asynchrones (fetch, setTimeout) qui ne sont pas encore arrivées.
Exemples JS
// ✗ Données async pas encore disponibles
let user;
fetch("/api/user").then(r => r.json()).then(d => { user = d; });
console.log(user.name); // TypeError ! fetch n'est pas encore terminé

// ✓ Attendre la promesse
const user = await fetch("/api/user").then(r => r.json());
console.log(user.name); // OK

// ✓ Optional chaining ?. (ES2020)
console.log(user?.address?.city); // undefined si user est null, pas d'erreur

// ✓ Nullish coalescing ?? pour valeur par défaut
const name = user?.name ?? "Inconnu";
JS ReferenceError Variable non déclarée / hors portée
Cause : utilisation d'une variable let/const avant sa déclaration (temporal dead zone), ou variable hors portée de bloc.
Exemples
// ✗ let/const non hoistées (temporal dead zone)
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 5;

// ✗ Variable de bloc inaccessible hors du bloc
if (true) {
  let result = 42;
}
console.log(result); // ReferenceError: result is not defined

// ✓ Déclarer dans la bonne portée
let result;
if (true) {
  result = 42;
}
console.log(result); // 42
JS Comportements silencieux (NaN, undefined, ==) JS ne lance pas d'erreur — il fait n'importe quoi
Cause : JavaScript tente de convertir les types au lieu de planter. Cela produit des valeurs silencieusement incorrectes comme NaN, undefined, ou des comparaisons inattendues.
Pièges JS classiques
// NaN — contagieux et silencieux
"abc" * 2         // → NaN (pas d'erreur !)
NaN === NaN        // → false  ← piège !
Number.isNaN(x)   // ✓ bonne façon de tester

// == vs === (toujours utiliser ===)
0  == ""   // → true  (coercition !)
0  === ""  // → false (correct)
null == undefined // → true

// typeof — prudence avec null
typeof null         // → "object" (bug historique JS !)
x === null          // ✓ seule façon fiable de tester null

// Array.sort() par défaut = alphabétique !
[10, 9, 2].sort()            // → [10, 2, 9] ← FAUX
[10, 9, 2].sort((a,b)=>a-b)  // → [2, 9, 10] ← correct

Erreurs C

C Segmentation fault Accès mémoire invalide
Cause : déréférencement d'un pointeur NULL ou invalide, accès hors des bornes d'un tableau, utilisation d'une mémoire déjà libérée (use after free).
Causes fréquentes
// ✗ Pointeur NULL non vérifié
char *p = malloc(100);
// si malloc échoue, p == NULL
p[0] = 'a';  // segfault si p est NULL !

// ✓ Toujours vérifier malloc
char *p = malloc(100);
if (p == NULL) { perror("malloc"); exit(1); }

// ✗ Accès hors tableau
int tab[5] = {0};
for (int i = 0; i <= 5; i++)  // ← <= au lieu de <
    tab[i] = i;  // segfault à i=5

// ✓ Détecter avec Valgrind ou AddressSanitizer
gcc -fsanitize=address -g main.c -o prog
./prog  # affiche où exactement la mémoire est corrompue
C Undefined Behavior (UB) Comportement imprévisible selon le compilateur
Cause : certaines opérations C produisent un comportement "non défini" par la norme — le programme peut faire n'importe quoi : planter, donner un résultat faux, ou sembler fonctionner correctement.
UB fréquents
// ✗ Variable non initialisée
int x;
printf("%d\n", x);  // UB : valeur aléatoire en mémoire

// ✗ Dépassement entier signé
int max = INT_MAX;
max + 1;  // UB en C (défini pour unsigned)

// ✗ Modification d'un littéral de chaîne
char *s = "hello";
s[0] = 'H';  // segfault ou UB (mémoire en lecture seule)
// ✓ Utiliser un tableau de chars
char s[] = "hello";  // copie modifiable

// Compiler avec tous les warnings !
gcc -Wall -Wextra -Wpedantic -g main.c
C Memory leak Mémoire allouée non libérée
Cause : chaque malloc() doit être suivi d'un free(). Oublier de libérer la mémoire provoque une fuite qui grossit au fil du temps — critique pour les programmes longs.
Détection avec Valgrind
char *tampon = malloc(256);
// utiliser tampon...
// oubli de free(tampon) → memory leak

// ✓ Toujours libérer, même en cas d'erreur
char *tampon = malloc(256);
if (!tampon) { exit(1); }
// ... traitement ...
free(tampon);       // obligatoire
tampon = NULL;      # bonus : évite use-after-free

// Détecter avec Valgrind :
valgrind --leak-check=full ./mon_programme
// == HEAP SUMMARY: 256 bytes in 1 blocks are definitely lost

Bugs logiques — les plus difficiles

🎭

Les bugs logiques ne provoquent aucune erreur — le programme tourne, mais produit un résultat incorrect. Ce sont les plus difficiles à trouver car il n'y a pas de traceback pour guider.

Tous Off-by-one error Décalage d'une unité dans les indices/bornes
Le bug le plus fréquent en programmation — une itération de trop ou de moins, un < vs <=, un index commençant à 0 ou 1.
Exemples
# ✗ Traite n-1 éléments au lieu de n
for i in range(len(lst) - 1):  # oubli du dernier
    traiter(lst[i])

# ✗ Calcul de longueur de sous-chaîne
s = "bonjour"
# longueur de s[2:5] → 5-2 = 3, pas 5-2+1

# ✓ Astuce : tester avec n=0, n=1, n=2
# Pour les boucles : vérifier la première et dernière itération
Tous Mutation d'une liste en itérant dessus Modifier une collection pendant qu'on la parcourt
Modifier une liste (append, remove, pop) pendant qu'on la parcourt avec un for provoque des sauts ou des doublons.
Correction
# ✗ Suppression pendant itération — saute des éléments
for item in liste:
    if item < 0:
        liste.remove(item)  # comportement imprévisible

# ✓ Itérer sur une copie
for item in liste[:]:  # copie avec [:]
    if item < 0:
        liste.remove(item)

# ✓ Encore mieux : list comprehension
liste = [x for x in liste if x >= 0]
Python Mutabilité partagée Alias non intentionnel sur les objets
En Python, les listes/dicts/objets sont passés par référence. Modifier une "copie" modifie aussi l'original si on n'a pas fait de vraie copie.
Copie vs alias
# ✗ Alias — modifie les deux
a = [1, 2, 3]
b = a          # b est un alias, pas une copie !
b.append(4)
print(a)       # → [1, 2, 3, 4] ← a a été modifié !

# ✓ Copie superficielle
b = a.copy()   # ou a[:]
b = list(a)

# ✓ Copie profonde (listes imbriquées)
import copy
b = copy.deepcopy(a)

# ✗ Argument mutable par défaut — piège classique !
def ajouter(item, liste=[]):  # [] créé UNE SEULE FOIS
    liste.append(item)
    return liste

ajouter(1)  # → [1]
ajouter(2)  # → [1, 2] ← pas [2] !

# ✓ Corriger avec None
def ajouter(item, liste=None):
    if liste is None: liste = []
    liste.append(item)
    return liste
Tous Problèmes de virgule flottante 0.1 + 0.2 ≠ 0.3
Les floats ne peuvent pas représenter exactement toutes les fractions décimales en binaire. Comparer des floats avec == est presque toujours une erreur.
Exemples et solutions
0.1 + 0.2 == 0.3  # → False !
print(0.1 + 0.2)  # → 0.30000000000000004

# ✓ Comparer avec une tolérance
import math
math.isclose(0.1 + 0.2, 0.3)  # → True
abs(a - b) < 1e-9              # tolérance manuelle

# ✓ Pour les calculs financiers : decimal
from decimal import Decimal
Decimal("0.1") + Decimal("0.2")  # → Decimal('0.3')

Déboguer la POO

Inspecter les objets
class Animal:
    def __init__(self, nom, age):
        self.nom = nom
        self.age = age

    # ✓ Toujours implémenter __repr__ pour le débogage
    def __repr__(self):
        return f"Animal(nom={self.nom!r}, age={self.age!r})"

a = Animal("Rex", 3)
print(a)  # Animal(nom='Rex', age=3)  ← utile dans print() et pdb

# Inspecter tous les attributs d'un objet
vars(a)       # → {'nom': 'Rex', 'age': 3}
a.__dict__    # idem
dir(a)        # liste TOUS les attributs + méthodes héritées

# Inspecter l'héritage
type(a)       # → <class '__main__.Animal'>
type(a).__mro__  # → MRO : ordre de résolution des méthodes
isinstance(a, Animal)   # → True
issubclass(Chien, Animal) # → True si Chien hérite de Animal

# Savoir d'où vient une méthode
a.afficher.__qualname__  # → 'Animal.afficher' ou 'Chien.afficher' ?
Bugs fréquents en POO Python
# ✗ Attribut de classe vs attribut d'instance
class Compteur:
    total = 0   # ← attribut de CLASSE, partagé par toutes les instances !
    def incrementer(self):
        Compteur.total += 1  # toutes les instances voient le même total

# ✓ Attribut d'instance dans __init__
class Compteur:
    def __init__(self):
        self.total = 0   # attribut de l'instance uniquement

# ✗ Oublier self — appel de méthode échoue
class Foo:
    def calculer(self):
        return helper()    # NameError : manque self.helper()

# ✗ super() oublié dans __init__ → attributs du parent manquants
class Chien(Animal):
    def __init__(self, nom, age, race):
        # oubli de super().__init__(nom, age) !
        self.race = race
    # → AttributeError: 'Chien' object has no attribute 'nom'

# ✓ Correction
class Chien(Animal):
    def __init__(self, nom, age, race):
        super().__init__(nom, age)  # ← obligatoire
        self.race = race

Cheat Sheet Débogage

🔍 Lire un traceback

PythonLire de bas en haut
JavaScriptLire de haut en bas
CFormat fichier:ligne:colonne
Plusieurs erreursCorriger la première d'abord
Erreur confuseChercher la vraie cause en amont

🛠️ Stratégies

print(f"{x=}")Debug rapide (Python 3.8+)
logging.debug()Debug production
assert cond, msgValider hypothèses
breakpoint()Lancer pdb
Binary searchIsoler par dichotomie
MRECas minimal de reproduction

⌨️ pdb essentiel

nLigne suivante
sEntrer dans la fonction
cContinuer jusqu'au BP
p xAfficher x
b 12, x>5BP conditionnel
qQuitter pdb

🐍 Erreurs Python

NameErrorVariable non définie / faute de frappe
TypeErrorMauvais type / None inattendu
IndexErrorHors bornes de liste
KeyErrorClé absente → utiliser .get()
AttributeErrorAttribut manquant / None
IndentationErrorMix tabs/espaces
Raccourcis VS Code Debugger
F5            # Lancer / Continuer
F9            # Poser / retirer un breakpoint
F10           # Step over (ligne suivante)
F11           # Step into (entrer fonction)
Shift+F11     # Step out (sortir fonction)
Shift+F5      # Arrêter
Ctrl+Shift+F5 # Redémarrer
Checklist avant de demander de l'aide
□ J'ai lu le traceback complet (pas juste la dernière ligne)
□ J'ai affiché les valeurs des variables au point du crash
□ J'ai un cas minimal qui reproduit le bug
□ J'ai vérifié le type réel de chaque variable impliquée
□ J'ai testé avec des valeurs limites (None, [], 0, "")
□ J'ai cherché le message d'erreur exact sur le web
□ J'ai essayé d'expliquer le bug à voix haute (rubber duck)