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
breakpoint(): pausar el món- Les ordres essencials (amb mnemotècnia)
- Cas pràctic: perseguint el bug de l'estoc dins de
vendre() - Post-mortem:
pytest --pdbs'obre on el test ha fallat - El depurador de VS Code: el mateix, amb finestres
- Bones pràctiques (i com no deixar una bomba a producció)
- 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 |
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'estocSessió 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=0fa que tots elsbreakpoint()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
asserto 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 limprimeix la variablel... si existeix; escriurela seques llista codi. Si la teva variable es diunoc, fes servirp n— i millor: no anomenisnuna 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
nisi "perdre't" dins d'una funció de la biblioteca estàndard després d'unasde més: no entris en pànic —r(return) executa fins a sortir de la funció actual, iuet 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
qesperant que el programa continuï:qavorta l'execució sencera (veuràsBdbQuit). Per a "deixa'm anar i segueix normal", l'ordre ésc. - 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 serw(on sóc?) i la segonappsobre 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 += unitatsExercici 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
- Introducció a Python
- Configuració de l'Entorn de Desenvolupament
- Sintaxi de Python i Tipus de Dades Bàsics
- Variables i Constants
- Entrada i Sortida Bàsica
- Entorns Virtuals i Gestió de Paquets
Mòdul 2: Estructures de Control
Mòdul 3: Funcions i Mòduls
- Definició de Funcions
- Arguments de Funció
- Funcions Lambda
- Mòduls i Paquets
- Visió General de la Biblioteca Estàndard
Mòdul 4: Estructures de Dades
Mòdul 5: Programació Orientada a Objectes
Mòdul 6: Gestió de Fitxers
- Lectura i Escriptura de Fitxers
- Treball amb Fitxers CSV
- Gestió de Dades JSON
- Operacions amb Fitxers i Directoris
Mòdul 7: Gestió d'Errors i Excepcions
- Introducció a les Excepcions
- Gestió d'Excepcions
- Llançament d'Excepcions
- Excepcions Personalitzades
- Bones Pràctiques i Registre d'Errors amb logging
Mòdul 8: Temes Avançats
- Anotacions de Tipus (type hints)
- Decoradors
- Generadors
- Gestors de Context
- Concurrència: Fils i Processos
- Asyncio per a la Programació Asíncrona
Mòdul 9: Proves i Depuració
- Introducció a les Proves
- Proves Unitàries amb unittest
- Proves amb pytest
- Desenvolupament Guiat per Proves
- Tècniques de Depuració
- Ús de pdb per a la Depuració
Mòdul 10: Desenvolupament Web amb Python
- Introducció al Desenvolupament Web
- Fonaments del Framework Flask
- Construcció d'APIs REST amb Flask
- Introducció a Django
- Construcció d'Aplicacions Web amb Django
Mòdul 11: Ciència de Dades amb Python
- Introducció a la Ciència de Dades
- NumPy per a la Computació Numèrica
- Pandas per a la Manipulació de Dades
- Matplotlib per a la Visualització de Dades
- Introducció a l'Aprenentatge Automàtic amb scikit-learn
