Els prints i els logs de la lliçó anterior responen preguntes que vas formular abans d'executar. Però a vegades necessites conversar amb el programa mentre corre: aturar-lo en una línia exacta, preguntar per qualsevol variable — fins i tot les que no se t'havia acudit vigilar —, avançar una línia, entrar dins d'una crida, i decidir el pas següent a la vista de l'anterior. Això és un depurador interactiu. Python en porta un de sèrie, pdb, i en aquesta lliçó el faràs servir per perseguir un bug d'estoc dins de vendre(), aprendràs a obrir-lo just on un test de pytest ha fallat, i el compararàs amb el depurador visual de VS Code. Amb ell es completa la xarxa de seguretat de Papyrus.

Contingut

  1. breakpoint(): pausar el món
  2. Les ordres essencials (amb mnemotècnia)
  3. Cas pràctic: perseguint el bug de l'estoc dins de vendre()
  4. Post-mortem: pytest --pdb s'obre on el test ha fallat
  5. El depurador de VS Code: el mateix, amb finestres
  6. Bones pràctiques (i com no deixar una bomba a producció)
  7. Tancament del mòdul: la xarxa de seguretat completa

breakpoint(): pausar el món

Des de Python 3.7, n'hi ha prou amb una funció incorporada:

def vendre(cataleg, titol, unitats):
    llibre = obtenir_llibre(cataleg, titol)
    breakpoint()          # ← l'execucio es PAUSA aqui
    ...

En arribar a aquesta línia, el programa s'atura i el terminal es converteix en una consola interactiva dins del programa viu, amb accés a totes les variables locals en aquell instant:

> .../papyrus/magatzem.py(57)vendre()
-> if not isinstance(unitats, int):
(Pdb) llibre.estoc
4
(Pdb) unitats
4

El prompt (Pdb) accepta ordres del depurador i expressions Python normals (llibre.estoc, unitats * llibre.preu, el que vulguis). breakpoint() és en realitat un embolcall configurable d'import pdb; pdb.set_trace(), la forma clàssica que veuràs en codi antic; avui es prefereix breakpoint() perquè és més curt i — com veuràs al final — desactivable des de fora. La línia que mostra -> és la següent que s'executarà: ets just abans d'ella, a temps de mirar-ho tot.

Les ordres essencials (amb mnemotècnia)

Amb deu ordres cobreixes el 95 % de les sessions. La lletra és la inicial anglesa — aquesta és tota la mnemotècnia que necessites:

Ordre Anglès (mnemotècnia) Què fa
l list Mostra el codi al voltant de la línia actual (ll = la funció sencera)
n next Executa la línia actual sense entrar a les seves crides
s step Executa la línia actual entrant a la funció que crida
c continue Reprèn fins al següent breakpoint (o el final)
b 62 break Posa un punt de ruptura a la línia 62 (b sol: els llista tots)
p expr print Avalua i imprimeix una expressió
pp expr pretty-print Com p, però formata bonic (dicts i objectes grossos)
w where Mostra la pila de crides i la teva posició en ella
u / d up / down Puja/baixa un marc a la pila (pots inspeccionar qui t'ha cridat!)
q quit Avorta l'execució i surt del depurador

La parella que més confon al principi: n passa per sobre, s es fica a dins. Si la línia actual és preu = llibre.preu_final(soci), amb n obtens preu ja calculat; amb s apareixes a la primera línia de preu_final per veure-la treballar per dins. I w/u/d són el traceback de 09-05 fet navegable: la mateixa pila, però pots passejar-t'hi amb les variables vives de cada marc.

Cas pràctic: perseguint el bug de l'estoc dins de vendre()

Després d'un refactor a magatzem.py, la suite canta:

FAILED tests/test_magatzem.py::test_vendre_tot_lestoc_deixa_zero
E   papyrus.errors.EstocInsuficientError: Estoc insuficient de "L'Odissea": demanades 4, disponibles 4

El missatge ja és sospitós: "insuficient" amb 4 demanades i 4 disponibles? Vendre exactament tot l'estoc és legal (el cas límit de 09-01). Reproduïm amb un cas mínim i plantem un breakpoint() a l'entrada de vendre, després de les validacions:

# minim.py
from papyrus.magatzem import vendre
from papyrus.models import Llibre

cataleg = {"L'Odissea": Llibre("L'Odissea", 12.50, 4)}
vendre(cataleg, "L'Odissea", 4)    # legal: exactament tot l'estoc

Sessió completa (les teves ordres després de (Pdb); anotada a la dreta):

$ python minim.py
> .../papyrus/magatzem.py(60)vendre()
-> if llibre.estoc <= unitats:
(Pdb) p llibre.estoc, unitats       # que compara exactament?
(4, 4)
(Pdb) p llibre.estoc <= unitats     # avaluem la condicio A MA
True                                # ← llançara l'excepcio!
(Pdb) ll                            # vegem la funcio sencera
 55     def vendre(cataleg, titol, unitats):
 ...
 60  ->     if llibre.estoc <= unitats:
 61             raise EstocInsuficientError(titol, unitats, llibre.estoc)
(Pdb) n                             # confirmem: next executa l'if...
> .../papyrus/magatzem.py(61)vendre()
-> raise EstocInsuficientError(titol, unitats, llibre.estoc)
(Pdb) q                             # vist: no cal res mes

Diagnòstic en quatre ordres: la condició diu <= on el contracte exigeix < — amb estoc 4 i 4 unitats demanades no falta estoc; en falta quan demanen més del disponible. És el clàssic off-by-one dels operadors de comparació. Fixa't en la jugada clau: p llibre.estoc <= unitats ha avaluat la condició sospitosa abans d'executar-la — cosa que cap print retrospectiu no pot fer. Es corregeix <=<, la suite torna al verd (el test que fallava ja era, ell mateix, la regressió que protegeix aquest límit), i de propina pots comprovar amb p que després de la venda llibre.estoc == 0.

Post-mortem: pytest --pdb s'obre on el test ha fallat

En el cas anterior hem editat el codi per plantar el breakpoint(). Hi ha una drecera millor quan el que falla és un test:

pytest --pdb                 # a la primera fallada, obre pdb AL punt de la fallada
pytest -x --pdb              # combinat amb -x: atura i depura el primer vermell

És una autòpsia (post-mortem): el test ja ha fallat, però pdb et deixa dins del cadàver amb totes les variables del moment de la fallada intactes — el catàleg del test, l'excepció, tot. Des d'allà, w t'ensenya la pila, u et puja del test a vendre, i p interroga el que calgui. Sense editar ni una línia. Per a codi que no és un test, existeix l'equivalent python -m pdb minim.py (executa sota el depurador des del principi i entra en mode post-mortem si rebenta). El flux professional amb pytest és: pytest detecta, pytest --lf --pdb et planta a l'escenari del crim.

El depurador de VS Code: el mateix, amb finestres

Tot l'anterior existeix en versió visual. A VS Code (amb l'extensió de Python): fas clic al marge esquerre d'una línia (apareix un punt vermell: un breakpoint visual, la b de pdb), llances amb F5 (o "Debug Test" sobre un test concret), i quan l'execució s'atura tens: panell de variables (tots els locals, sense demanar-los amb p), watch (expressions vigilades que es reavaluen a cada pas), la pila de crides clicable (el w/u/d), i botons per a step over (n), step into (s) i continue (c). Els conceptes són idèntics — per això hem après pdb primer: qui entén n/s/c entén qualsevol depurador de qualsevol llenguatge.

Quin fer servir? No és religió, és context:

Situació Millor opció
Explores un bug complex a la teva màquina, amb calma VS Code: veure tots els locals alhora accelera la formació d'hipòtesis
Ets en un servidor per SSH, sense entorn gràfic pdb: sempre hi és, no necessita res
Un test concret falla i vols el context exacte pytest --pdb (o "Debug Test" a VS Code)
Sessió ràpida: una condició sospitosa, dues preguntes breakpoint() + pdb: plantat en 5 segons
Vols avaluar expressions arbitràries sobre la marxa Empat: consola (Pdb) o la Debug Console de VS Code

Bones pràctiques (i com no deixar una bomba a producció)

  • No deixis mai un breakpoint() en codi que entregues. És la versió nuclear del print oblidat: el programa d'un altre s'aturarà esperant una consola que potser no existeix. Abans de donar per tancada una sessió de depuració, busca i elimina: grep -rn "breakpoint()" papyrus/ (amb Grep/Select-String al teu entorn).
  • La xarxa de seguretat externa: la variable d'entorn PYTHONBREAKPOINT=0 fa que tots els breakpoint() s'ignorin sense tocar codi — es fa servir com a cinturó en entorns de producció. Que existeixi no t'eximeix de netejar; et salva del dia que no vas netejar.
  • Deixa el depurador millor que com el vas trobar: si durant la sessió has descobert un valor que "hauria de ser impossible", converteix-lo en assert o en test abans de tancar. La sessió de pdb és efímera; el test queda.
  • No depuris el que un log ja respon. Obrir pdb per saber "s'ha arribat a executar aquesta funció?" és matar mosques a canonades: això és un logger.debug (09-05). El depurador és per a preguntes que requereixen l'estat complet i la capacitat d'avançar.
  • A pdb, compte amb les variables que es diuen com les ordres: p l imprimeix la variable l... si existeix; escriure l a seques llista codi. Si la teva variable es diu n o c, fes servir p n — i millor: no anomenis n una variable (M1 ja et demanava noms amb significat).

Tancament del mòdul: la xarxa de seguretat completa

Mira la xarxa que Papyrus ha teixit entre els mòduls 8 i 9 — quatre capes, cadascuna caçant el que se li escapa a l'anterior:

Capa Eina Què caça Quan actua
Tipus mypy + type hints (08-01) Contractes de forma: un str on va un int Abans d'executar
Comportament Suite de tests: pytest (09-01→09-04) Contractes de valor: 12.35, no 12.36 A cada canvi, en segons
Diagnòstic en viu pdb / VS Code (09-06) El perquè d'una fallada ja reproduïda Durant la caça del bug
Memòria logging a papyrus.log (M7, 09-05) El que va passar quan no miraves Sempre, també a producció

Cap capa no substitueix les altres: mypy no sap que el descompte és del 5 %, els tests no expliquen per què fallen, pdb no protegeix el futur i els logs no impedeixen res. Juntes, converteixen "canviar una línia de Papyrus" — la pregunta que va obrir aquest mòdul — en un acte sense por: si alguna cosa es trenca, alguna capa cridarà, i sabràs on, quan i per què.

Errors Comuns i Consells

  • Confondre n i s i "perdre't" dins d'una funció de la biblioteca estàndard després d'una s de més: no entris en pànic — r (return) executa fins a sortir de la funció actual, i u et recorda des d'on cridaves.
  • Prémer Enter sense ordre: pdb repeteix l'última ordre. És utilíssim per avançar amb n, n, Enter, Enter... i desconcertant si no ho saps (per què avança si no he escrit res?).
  • Depurar la versió equivocada del codi: si edites un fitxer mentre pdb és obert, la sessió continua executant el codi antic (ja carregat). Surt, reexecuta i torna a entrar.
  • Fer servir q esperant que el programa continuï: q avorta l'execució sencera (veuràs BdbQuit). Per a "deixa'm anar i segueix normal", l'ordre és c.
  • Abusar del depurador com a substitut de tests: si cada canvi requereix una sessió de pdb per convèncer-te que funciona, et falten tests. El depurador diagnostica; la suite garanteix.
  • Consell: a pytest --pdb, la teva primera ordre gairebé sempre hauria de ser w (on sóc?) i la segona pp sobre l'estructura de dades central (aquí, pp cataleg). Orientar-te abans de moure't estalvia la meitat de la sessió.

Exercicis

Exercici 1

Sense executar res, escriu la seqüència exacta d'ordres pdb per a aquesta missió: estàs aturat amb breakpoint() a la primera línia de cobrar_a_soci (a caixa.py), que més avall crida vendre. Vols: (a) veure la funció sencera, (b) avançar fins a la línia de la crida a vendre (dues línies més avall), (c) entrar dins de vendre, (d) un cop dins, veure la pila per confirmar des d'on has arribat, (e) imprimir amb format el catàleg complet, (f) continuar l'execució normal fins al final.

Exercici 2

Un company et passa aquest diff com a "arranjament" després d'una sessió de depuració. Assenyala els dos problemes que no haurien d'arribar al repositori i explica què faria cadascun a producció:

 def reposar(cataleg, titol, unitats):
     llibre = obtenir_llibre(cataleg, titol)
+    breakpoint()
     if not isinstance(unitats, int) or unitats <= 0:
         raise ValueError(f"Unitats no valides: {unitats}")
+    print(f"DEBUG {titol=} {unitats=}")
     llibre.estoc += unitats

Exercici 3

El test test_tancament_de_caixa_suma_nomes_files_valides (09-03) falla després d'un refactor del pipeline de generadors. Descriu, pas a pas, com faries servir pytest per arribar al punt de la fallada sense editar cap fitxer, i quines tres primeres preguntes (expressions per a p/pp) faries un cop dins.

Solucions

Exercici 1

(Pdb) ll        # (a) la funcio sencera, amb la fletxa a la linia actual
(Pdb) n         # (b) avança una linia...
(Pdb) n         #     ...i una altra: la fletxa apunta ara a la crida a vendre
(Pdb) s         # (c) step: entra DINS de vendre
(Pdb) w         # (d) la pila: jornada.py → cobrar_a_soci → vendre (tu ets a baix)
(Pdb) pp cataleg    # (e) el cataleg, formatat
(Pdb) c         # (f) continue: allibera el programa fins al final

(A (b) també val b 33 + c si coneixes el número de línia: plantar un break i continuar fins a ell — més còmode quan hi ha deu línies pel mig.)

Exercici 2 — Problema 1: el breakpoint(). A producció, reposar s'aturaria esperant l'entrada d'un depurador que ningú atén: Papyrus congelat en plena reposició (mitigable amb PYTHONBREAKPOINT=0 a l'entorn, però la bomba no ha d'embarcar). Problema 2: el print de depuració, que embruta la sortida estàndard de qualsevol programa que faci servir el paquet (imagina't el tancament de caixa imprimint centenars de línies DEBUG). Si aquesta traça té valor permanent, la seva forma correcta és logger.debug("reposar: %r +%d", titol, unitats) — silenciosa per defecte, activable quan calgui (09-05).

Exercici 3 — Passos: (1) pytest --lf per confirmar que només falla aquest test i veure el missatge; (2) pytest --lf --pdb — en fallar, pdb s'obre al punt exacte de la fallada amb l'estat intacte; (3) orientació: w per veure la pila (ha fallat a l'assert del test o dins de tancament_de_caixa? amb u/d et mous al marc interessant). Tres primeres preguntes típiques: p ruta.read_text() (què contenia exactament el CSV del test?), pp sobre l'estructura intermèdia del pipeline si ets en un marc de tancament_de_caixa (per exemple, materialitzar p list(parsejar(llegir_vendes(ruta))) per veure quines files han sobreviscut al parseig — compte: consumir un generador a pdb l'esgota per a la resta de l'execució, legítim en una autòpsia), i p total o l'expressió de l'assert per veure el valor que s'ha comparat. Amb això sabràs si el refactor perd files, parseja malament les quanties o filtra de més.

Conclusió

El mòdul 9 ha respost la pregunta amb què el mòdul 8 ens va deixar en suspens. Ara, quan l'Anna canviï una línia, no creua els dits: executa pytest i en segons sap si els quatre preus de soci continuen sent 12.35, 9.83, 15.71 i 20.75 (09-01, 09-02, 09-03). Els contractes de vendre — la quantia amb IVA, l'estoc intacte després d'un EstocInsuficientError — han deixat de ser promeses i són tests que fallen en vermell si algú les trenca. Els cupons van néixer al revés i millor: test primer, codi mínim després, refactor sense por (09-04). I quan alguna cosa es trenca igualment, hi ha mètode — reproduir, aïllar, corregir, protegir amb una regressió (09-05) — i hi ha lupa: breakpoint(), quatre ordres de pdb i la condició sospitosa avaluada en viu abans d'executar-se (09-06). Amb mypy vigilant els tipus, la suite vigilant el comportament, el depurador per al perquè i papyrus.log com a memòria, Papyrus té per fi una xarxa de seguretat completa. Però aixeca la vista: tot això — catàleg, vendes, cupons, tancaments — viu en un sol lloc, l'ordinador de l'Anna, i només ella pot fer-lo servir. En Lluís voldria consultar l'estoc des de casa; la Júlia, reservar un llibre des del mòbil. La següent frontera no és fer que Papyrus funcioni millor, sinó obrir-lo al món: convertir-lo en una aplicació web. Aquest és el mòdul 10.

Curs de Programació en Python

Mòdul 1: Introducció a Python

Mòdul 2: Estructures de Control

Mòdul 3: Funcions i Mòduls

Mòdul 4: Estructures de Dades

Mòdul 5: Programació Orientada a Objectes

Mòdul 6: Gestió de Fitxers

Mòdul 7: Gestió d'Errors i Excepcions

Mòdul 8: Temes Avançats

Mòdul 9: Proves i Depuració

Mòdul 10: Desenvolupament Web amb Python

Mòdul 11: Ciència de Dades amb Python

Mòdul 12: Projecte Final

© Copyright 2026. Tots els drets reservats