TascaFàcil funciona: la v0.16 té classes amb validació, una agenda que protegeix les seves dades i persistència en JSON. Però tot això viu en un únic fitxer que ja acumula tres classes, una dotzena de funcions d'entrada i sortida, les constants, el menú i la persistència. Cada vegada que cal tocar alguna cosa toca recórrer-lo sencer buscant on, i si demà la Marta vol una versió web, no hi ha manera de reutilitzar la lògica sense arrossegar també el menú de la consola. Aquesta lliçó resol aquest problema amb l'última peça d'organització que li falta al curs: repartir el codi en diversos fitxers que s'importen entre si. Veuràs què és un mòdul i què passa exactament quan Python n'importa un —cosa que explica per fi l'if __name__ == "__main__": que arrosseguem des de 04-04—, com s'agrupen diversos mòduls en un paquet, quins tresors porta la biblioteca estàndard i com s'instal·len llibreries de tercers amb pip. Al final, TascaFàcil deixarà de ser un fitxer per convertir-se en el paquet tascafacil/.

Contingut

  1. Què és un mòdul i per què separar
  2. Les formes d'importar
  3. Com troba Python els mòduls
  4. Què passa en importar: __name__ i if __name__ == "__main__"
  5. Paquets: carpetes amb __init__.py
  6. La biblioteca estàndard
  7. Paquets de tercers: PyPI i pip
  8. Importacions circulars
  9. TascaFàcil v0.17: el paquet tascafacil/
  10. Errors comuns i consells
  11. Exercicis
  12. Conclusió

  1. Què és un mòdul i per què separar

Un mòdul és, simplement, un fitxer .py. No cal declarar res ni marcar-lo de cap manera: tan bon punt deses validacions.py, ja tens un mòdul anomenat validacions que qualsevol altre fitxer pot importar. El fas servir des del principi del curs sense saber-ho: import json, import csv, from pathlib import Path carreguen mòduls escrits per altres.

Per què repartir el codi en diversos fitxers? Per quatre raons molt concretes:

  • Trobar les coses. En un projecte amb model.py, agenda.py, magatzem.py i interficie.py, saber on tocar és immediat. En un fitxer de mil línies, no.
  • Reutilitzar. Si la lògica de tasques viu a model.py sense ni una crida a print ni a input, aquell fitxer serveix igual per a l'aplicació de consola, per a un web o per a un script d'informes.
  • Treballar en equip. Dues persones en dos fitxers diferents no es trepitgen; en el mateix fitxer, sí.
  • Limitar el dany. Un mòdul amb una responsabilitat clara es pot llegir, entendre i canviar sense tenir tota l'aplicació al cap.

El criteri per decidir què va a cada fitxer és el mateix que ja vas fer servir amb les funcions a 04-04 i amb les classes en aquest mòdul: una responsabilitat per mòdul. Les dades per una banda, la persistència per una altra, la conversa amb l'usuari per una altra.

  1. Les formes d'importar

Suposem un fitxer formats.py amb una constant i una funció:

# formats.py
AMPLE = 52

def titol(text):
    """Retorna el text centrat i en majuscules."""
    return text.upper().center(AMPLE)

Des d'un altre fitxer, a la mateixa carpeta, s'hi pot accedir de quatre maneres:

Forma Com es fa servir després Quan convé
import formats formats.titol("hola") La més segura: queda clar d'on surt cada nom
import formats as fmt fmt.titol("hola") Quan el nom del mòdul és llarg (import numpy as np)
from formats import titol titol("hola") Quan fas servir un o dos noms moltes vegades
from formats import titol as tit tit("hola") Per evitar un xoc de noms
from formats import * titol("hola") Desaconsellat
import formats
print(formats.titol("agenda d avui"))        # el prefix diu d on ve
from formats import titol, AMPLE
print(titol("agenda d avui"), AMPLE)         # sense prefix, mes curt

L'última fila de la taula mereix una explicació. from modul import * porta tots els noms públics del mòdul al fitxer actual, i està desaconsellat per dos motius seriosos: qui llegeixi el teu codi no pot saber d'on surt titol —l'has definit tu, ve de formats o d'un altre import *?—, i si dos mòduls exporten el mateix nom, el segon trepitja el primer sense dir res. Un from os import * seguit d'un from numpy import * pot deixar-te amb una funció diferent de la que et penses que crides. És un estalvi de teclejar que es paga amb hores de depuració. La regla pràctica que segueixen gairebé tots els projectes: import modul per defecte, from modul import nom quan es fa servir molt un nom concret, i mai *.

  1. Com troba Python els mòduls

Quan escrius import formats, Python busca aquell fitxer en una llista de carpetes, en ordre:

  1. La carpeta de l'script que has executat (o el directori actual, a la consola interactiva).
  2. Les carpetes indicades a la variable d'entorn PYTHONPATH, si existeix.
  3. Les carpetes de la instal·lació de Python, on hi ha la biblioteca estàndard.
  4. La carpeta site-packages, on pip instal·la els paquets de tercers.

Aquesta llista és visible i es pot consultar:

import sys
for carpeta in sys.path:
    print(carpeta)

Si Python no troba el mòdul, l'error és inconfusible: ModuleNotFoundError: No module named 'formats'. Les causes habituals són tres: el fitxer no és a la carpeta que et penses, el nom està mal escrit (majúscules incloses: en Linux Formats i formats són diferents), o has executat l'script des d'una altra carpeta.

I hi ha un parany que cal conèixer sí o sí: no anomenis mai els teus fitxers com un mòdul de la biblioteca estàndard. Si crees un random.py per practicar i a dins escrius import random, Python trobarà el teu fitxer abans que l'oficial —la carpeta de l'script va primer— i fallarà amb un error incomprensible del tipus AttributeError: module 'random' has no attribute 'randint'. El mateix amb json.py, csv.py, math.py, time.py o email.py. Si et passa, canvia el nom del teu fitxer i esborra la carpeta __pycache__ que Python hagi deixat al costat.

  1. Què passa en importar: __name__ i if __name__ == "__main__"

Aquí arriba l'explicació que vam prometre a 04-04. Quan Python importa un mòdul, n'executa el codi de dalt a baix, sencer, una sola vegada. Les línies def i class defineixen funcions i classes sense executar-les, però qualsevol altra línia sí que s'executa: assignacions, print, crides.

I en aquesta execució, Python defineix al mòdul una variable especial anomenada __name__, amb un valor que depèn de com s'hi hagi arribat:

Situació Valor de __name__
El fitxer s'executa directament (python programa.py) "__main__"
El fitxer s'importa des d'un altre (import programa) "programa" (el seu nom de mòdul)

Comprovem-ho amb dos fitxers:

# salutacio.py
print(f"Executant salutacio.py, __name__ val: {__name__}")

def saludar(nom):
    return f"Hola, {nom}."

if __name__ == "__main__":
    print(saludar("Marta"))          # nomes si executem AQUEST fitxer
# principal.py
import salutacio
print(f"Executant principal.py, __name__ val: {__name__}")
print(salutacio.saludar("Luis"))

En executar python salutacio.py surten dues línies: Executant salutacio.py, __name__ val: __main__ i Hola, Marta.. Però en executar python principal.py la sortida és aquesta:

Executant salutacio.py, __name__ val: salutacio
Executant principal.py, __name__ val: __main__
Hola, Luis.

Llegeix-la amb calma, perquè conté tres lliçons:

  • El print de salutacio.py es va executar encara que només l'haguem importat: importar és executar. Per això un mòdul no ha de fer feina pesada ni demanar dades a l'usuari al seu nivell superior.
  • __name__ va valer "salutacio", no "__main__", així que la línia print(saludar("Marta")) no es va executar. Aquest és exactament el propòsit de l'if: el codi que només té sentit en executar el fitxer directament.
  • __name__ val "__main__" al fitxer que ho va arrencar tot, sigui quin sigui el seu nom.

D'aquí en surt la conclusió que arrossegàvem: if __name__ == "__main__": main() significa «arrenca l'aplicació només si m'estan executant a mi; si algú m'importa per reutilitzar les meves funcions, no facis res». Sense aquesta guarda, importar tascafacil per fer servir la seva classe Tasca llançaria el menú interactiu per sorpresa.

Un últim detall: en importar per primera vegada, Python desa una versió compilada a __pycache__ per accelerar les execucions següents, i no torna a executar el mòdul si ja està importat, encara que apareguin deu import iguals.

  1. Paquets: carpetes amb __init__.py

Quan els mòduls es multipliquen, s'agrupen en carpetes. Una carpeta que Python tracta com un conjunt de mòduls és un paquet, i per marcar-la com a tal se li afegeix un fitxer anomenat __init__.py.

graph TD
    A["projecte/"] --> B["main.py"]
    A --> C["tascafacil/"]
    C --> D["__init__.py"]
    C --> E["model.py"]
    C --> F["agenda.py"]
    C --> G["magatzem.py"]
    C --> H["interficie.py"]

Amb aquesta estructura, els mòduls s'importen amb la notació del punt: import tascafacil.model per al mòdul complet, from tascafacil.model import Tasca per a un nom concret o from tascafacil import agenda per a un mòdul del paquet. L'__init__.py s'executa en importar qualsevol cosa del paquet, i serveix per a dues coses: deixar-lo buit (el més habitual i perfectament correcte), o fer-lo servir per exposar una interfície còmoda, de manera que qui faci servir el paquet no hagi de conèixer-ne l'estructura interna:

# tascafacil/__init__.py
"""Paquet TascaFacil: gestor de tasques de l'Estudi Alba."""
from .model import Tasca, TascaRecurrent
from .agenda import Agenda

__version__ = "0.17"

Gràcies a això, qui faci servir el paquet pot escriure from tascafacil import Tasca, Agenda sense saber en quin fitxer és cada classe. El que no ha de fer mai un __init__.py és feina pesada, perquè s'executa a cada importació. I fixa't en el punt de from .model import Tasca: és una importació relativa, i significa «el mòdul model que és al meu mateix paquet».

Tipus Exemple Quan fer-la servir
Absoluta from tascafacil.model import Tasca Per defecte: es llegeix sense ambigüitat des de qualsevol lloc
Relativa from .model import Tasca Dins del mateix paquet; .. puja un nivell

Totes dues funcionen i les veuràs en projectes reals. La guia d'estil oficial recomana les absolutes per claredat, amb les relatives com a opció acceptable dins d'un paquet gran. El que no convé és barrejar els dos estils sense criteri al mateix projecte.

  1. La biblioteca estàndard

Python ve «amb les piles posades»: en instal·lar-lo tens centenars de mòduls ja disponibles, sense instal·lar res. Aquests són els que més et serviran ara:

Mòdul Per a què serveix
math Arrels, potències, arrodoniments precisos, constants com pi
random Números aleatoris, choice, shuffle, sample
datetime Dates i hores: càlculs, diferències i formats
os Sistema operatiu: variables d'entorn, processos, rutes antigues
pathlib Rutes modernes orientades a objectes: Path, exists(), read_text()
json Llegir i escriure JSON (05-05)
csv Llegir i escriure CSV, amb DictReader i DictWriter (05-05)
sys Intèrpret: sys.path, sys.argv, sys.exit()
time Pauses amb sleep i mesura amb perf_counter (06-02)
statistics mean, median, stdev sense instal·lar res
collections Counter, defaultdict, namedtuple, deque
functools lru_cache (06-03), partial, reduce

Un exemple amb datetime aplicat a les nostres tasques, que és l'ús que més falta li fa a TascaFàcil: passar de «dies estimats» a dates de lliurament reals.

from datetime import date, timedelta

avui = date.today()                              # date(2026, 8, 5)
lliurament = avui + timedelta(days=3)            # sumar dies es aixi de simple
print(lliurament.strftime("%d/%m/%Y"))           # 08/08/2026

queden = (lliurament - avui).days                # restar dates dona un timedelta
print(f"Queden {queden} dies.")                  # Queden 3 dies.
print(lliurament < avui)                         # False: les dates es comparen

Tres peces: date.today() dona la data actual, timedelta(days=n) representa una durada que se suma o es resta a una data, i strftime la formata (%d dia, %m mes, %Y any). Restar dues dates retorna un timedelta, el .days del qual dona la diferència. I com que les dates es comparen amb < i >, ordenar tasques per data de lliurament amb sorted(..., key=attrgetter("lliurament")) funciona sense més.

  1. Paquets de tercers: PyPI i pip

Quan la biblioteca estàndard no arriba, es recorre a PyPI (Python Package Index), el repositori públic on la comunitat publica més de mig milió de paquets: requests per parlar amb webs, pandas per analitzar dades, flask i django per a aplicacions web, pytest per a proves.

S'instal·len amb pip, el gestor de paquets que ve amb Python:

pip install requests                 # afegir un paquet
pip install requests==2.31.0         # una versio concreta
pip list                             # veure els paquets presents
pip freeze > requirements.txt        # desar la llista exacta de versions
pip install -r requirements.txt      # refer l entorn en un altre ordinador
pip uninstall requests               # treure un paquet

El fitxer requirements.txt és la peça clau del treball en equip: conté les dependències amb la seva versió exacta, es desa al costat del codi i permet que qualsevol reprodueixi el teu entorn amb una sola comanda.

I aquí convé recordar l'entorn virtual d'Entorns de desenvolupament. Els paquets s'han d'instal·lar dins del venv del projecte, no al Python del sistema, per dos motius: dos projectes poden necessitar versions diferents de la mateixa llibreria, i així el requirements.txt reflecteix exactament el que aquell projecte fa servir i res més.

Un advertiment de seguretat que ja no hauries d'oblidar mai: instal·lar un paquet és executar codi d'un desconegut al teu ordinador. Abans d'un pip install, comprova que el nom estigui ben escrit —hi ha paquets maliciosos amb noms gairebé idèntics als populars, a la caça d'errades—, mira si el projecte té activitat recent i usuaris, i desconfia de dependències que apareixen sense que sàpigues qui les ha demanat. TascaFàcil, per cert, no en necessita cap: tot el que fa servir és a la biblioteca estàndard.

  1. Importacions circulars

Passa quan dos mòduls s'importen mútuament: agenda.py fa import magatzem i magatzem.py fa import agenda. Python comença a carregar el primer, veu l'import del segon, comença a carregar el segon, que torna al primer... que encara està a mig executar, i per tant li falten noms. El resultat és un ImportError: cannot import name 'X' from partially initialized module que desconcerta qualsevol.

La solució no és tècnica sinó de disseny: les dependències han d'anar en una sola direcció. A TascaFàcil, magatzem sap de model, i interficie sap de tots dos, però model no sap res de ningú. Si et trobes amb un cicle, gairebé sempre vol dir que:

  • Les responsabilitats estan mal repartides i hi ha alguna cosa que hauria de ser en un tercer mòdul del qual depenguin tots dos.
  • O una de les dues direccions sobra: normalment el mòdul «de baix» no necessita conèixer el de dalt, sinó rebre el que li calgui com a paràmetre.

  1. TascaFàcil v0.17: el paquet tascafacil/

Repartim la v0.16 en cinc mòduls, cadascun amb una responsabilitat:

graph TD
    A["tascafacil/"] --> B["__init__.py"]
    A --> C["model.py<br/>Tasca, TascaRecurrent"]
    A --> D["agenda.py<br/>Agenda"]
    A --> E["magatzem.py<br/>JSON i CSV"]
    A --> F["interficie.py<br/>menu i entrada/sortida"]
    A --> G["__main__.py<br/>main()"]

I aquestes són les capçaleres de cadascun, que mostren qui depèn de qui:

# tascafacil/model.py --- no importa res del projecte: es la base
PRIORITATS = ("alta", "mitjana", "baixa")
EQUIP = ("marta", "luis", "nuria")
ORDRE_PRIORITAT = {"alta": 0, "mitjana": 1, "baixa": 2}

class Tasca: ...
class TascaRecurrent(Tasca): ...

# tascafacil/agenda.py
from .model import Tasca, ORDRE_PRIORITAT

class Agenda: ...                       # sense desar() ni carregar(): aixo es del magatzem
# tascafacil/magatzem.py
import csv, json
from pathlib import Path
from .model import Tasca
from .agenda import Agenda

RUTA_JSON = Path("tasques.json")
RUTA_CSV = Path("tasques.csv")
CAMPS = ("titol", "responsable", "prioritat", "dies", "fets", "completada")

def desar(agenda, ruta=RUTA_JSON): ...
def carregar(ruta=RUTA_JSON): ...       # retorna una Agenda
def exportar_csv(agenda, ruta=RUTA_CSV): ...
# tascafacil/interficie.py
from .model import PRIORITATS, EQUIP, Tasca
from .agenda import Agenda

AMPLE = 52
OPCIONS = ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10")

def demanar_text(missatge): ...         # i demanar_opcio, demanar_enter, confirmar
def mostrar_menu(): ...                 # i mostrar_llistat, mostrar_fitxa
# tascafacil/__main__.py
from . import magatzem, interficie
from .model import Tasca

def main():
    """Executa el bucle principal de TascaFacil."""
    agenda = magatzem.carregar()
    while True:
        interficie.mostrar_menu()
        opcio = interficie.demanar_opcio("Tria una opcio (1-10): ", interficie.OPCIONS)
        if opcio == "10" and interficie.confirmar("Segur que vols sortir?"):
            magatzem.desar(agenda)
            break
        # ... la resta d opcions

if __name__ == "__main__":
    main()

I així s'executa ara el programa, des de la carpeta que conté tascafacil/:

python -m tascafacil

L'opció -m diu a Python «executa aquest paquet com a programa», i per això el fitxer es diu __main__.py: és el que Python busca per arrencar un paquet. L'alternativa clàssica és deixar fora de la carpeta un main.py de tres línies —from tascafacil.__main__ import main, i la guarda if __name__ == "__main__": main()— i executar python main.py. Fixa't en el resultat, que és el que importa de tota la lliçó:

Mòdul Responsabilitat De qui depèn?
model.py Què és una tasca i què sap fer De ningú
agenda.py La col·lecció i les seves operacions De model
magatzem.py Llegir i escriure fitxers De model i agenda
interficie.py Parlar amb l'usuari De model i agenda
__main__.py Coordinar el flux De tots

Les fletxes van en una sola direcció, així que no hi ha importacions circulars. I model.py, que no depèn de res i no té ni un sol print, es pot reutilitzar tal qual en una versió web o provar de manera aïllada, que és just el que farem a 08-04.

Errors Comuns i Consells

  • Anomenar un fitxer com un mòdul estàndard (random.py, json.py, csv.py). Python carregarà el teu i l'error serà incomprensible. Canvia-li el nom i esborra __pycache__.
  • Fer servir from modul import *. Destrueix la traçabilitat dels noms i provoca xocs silenciosos.
  • Executar el paquet des de dins de la seva carpeta. python __main__.py des de dins de tascafacil/ trenca les importacions relatives (attempted relative import with no known parent package). Situa't a la carpeta de sobre i fes servir python -m tascafacil.
  • Posar codi executable al nivell superior d'un mòdul. Recorda que importar és executar: un input() o un print solts es dispararan tan bon punt algú importi el fitxer. Tot el que sigui executable, dins de funcions i sota if __name__ == "__main__":.
  • Crear cicles entre mòduls. Si a importa b i b importa a, revisa el repartiment de responsabilitats: gairebé sempre sobra una de les dues direccions.
  • Instal·lar paquets fora de l'entorn virtual. Acabes amb un Python del sistema ple de llibreries i un requirements.txt que no reflecteix el projecte.
  • Consell: comença per un fitxer i separa quan faci mal. Dividir massa aviat crea deu fitxers de vint línies i un embolic d'importacions. El senyal per separar és concret: quan costi trobar on tocar o quan vulguis reutilitzar una part sense les altres.

Exercicis

Exercici 1: Mòdul, importació i __name__

Crea mesures.py amb una constant IVA = 0.21, una funció amb_iva(base) que retorni l'import amb IVA i un print("carregant mesures") al nivell superior. Crea després factura.py que l'importi i calculi el total d'un pressupost de 1.200 € per al client Vidal. Prediu què imprimirà cadascun en executar-se directament i comprova-ho; després afegeix a mesures.py una prova ràpida protegida amb if __name__ == "__main__": i explica què canvia.

Exercici 2: Repartir responsabilitats

Un company ha escrit un únic fitxer gestor.py de 600 línies amb: la classe Client, la classe Factura, funcions per llegir i escriure el CSV de factures, funcions que demanen dades per teclat, el menú i el main(). Proposa un repartiment en mòduls, indica què importa cadascun i dibuixa mentalment les fletxes de dependència per comprovar que no hi ha cicles.

Exercici 3: Dates de lliurament amb datetime

Escriu una funció data_lliurament(dies) que retorni la data de lliurament a partir d'avui formatada com a dd/mm/aaaa, i una altra dies_fins(data) que rebi un date i retorni quants dies falten (negatiu si ja ha passat). Fes-les servir per imprimir el pla de les tres tasques d'Estudi Alba: cartell (3 dies), logotip (5) i pressupost (2).

Solucions

Solució 1.

# mesures.py
print("carregant mesures")
IVA = 0.21

def amb_iva(base):
    """Retorna l'import amb l'IVA aplicat."""
    return round(base * (1 + IVA), 2)

if __name__ == "__main__":
    print(amb_iva(100))          # 121.0: prova rapida, nomes en executar aquest fitxer

# factura.py
import mesures
print(f"Pressupost client Vidal: {mesures.amb_iva(1200)} EUR")

En executar python factura.py la sortida és carregant mesures i després Pressupost client Vidal: 1452.0 EUR. El primer missatge apareix encara que no l'haguem demanat, perquè importar és executar; el 121.0 de la prova no apareix, perquè en importar-se __name__ val "mesures" i no "__main__". Aquesta guarda és el que permet que un mòdul tingui la seva pròpia comprovació ràpida sense molestar qui el reutilitza. I el print("carregant mesures") solt és justament el que no ha de fer un mòdul de veritat: aquí hi és només per veure-ho.

Solució 2.

Mòdul Contingut Importa
model.py Classes Client i Factura Res del projecte
magatzem.py Llegir i escriure el CSV de factures model
interficie.py Demanar dades per teclat i mostrar el menú model
principal.py El main() i el bucle d'opcions Els tres anteriors

Les fletxes van de principal cap als altres i de magatzem/interficie cap a model: cap no torna enrere, així que no hi ha cicles. La clau del repartiment és que model.py no importi res del projecte ni contingui ni un sol print o input; si Factura necessités demanar alguna cosa a l'usuari, seria senyal que aquella lògica és al lloc equivocat. Si les quatre peces creixen, el pas següent és agrupar-les en un paquet gestor/ amb el seu __init__.py.

Solució 3.

from datetime import date, timedelta

def data_lliurament(dies):
    """Retorna la data de lliurament d aqui a 'dies', com a dd/mm/aaaa."""
    return (date.today() + timedelta(days=dies)).strftime("%d/%m/%Y")

def dies_fins(data):
    """Dies que falten per a una data; negatiu si ja ha passat."""
    return (data - date.today()).days

for titol, dies in (("Cartell fira del llibre", 3), ("Logotip Sole", 5),
                    ("Pressupost client Vidal", 2)):
    print(f"{titol:<28} es lliura el {data_lliurament(dies)}")
print(dies_fins(date(2026, 1, 1)))       # negatiu: aquesta data ja ha passat

Dos detalls que convé fixar. date.today() + timedelta(days=dies) retorna un objecte date nou: les dates són immutables, com les tuples i les cadenes, així que mai no es modifiquen al lloc. I strftime s'aplica només al final, per mostrar: mentre el programa calcula, convé treballar sempre amb objectes date i no amb textos, perquè els objectes es comparen, es resten i s'ordenen, i "08/08/2026" no.

Conclusió

Un mòdul és qualsevol fitxer .py, i separar el codi en diversos respon a quatre necessitats molt concretes: trobar les coses, reutilitzar la lògica sense arrossegar la interfície, treballar en equip i limitar el dany de cada canvi. S'importen amb import modul —la forma més clara, perquè el prefix diu d'on surt cada nom—, import modul as alies, from modul import nom i la seva variant amb as, evitant sempre from modul import *, que esborra la traçabilitat i provoca xocs silenciosos. Python busca els mòduls a la carpeta de l'script, a PYTHONPATH, a la instal·lació i a site-packages, llista visible a sys.path; d'aquí el ModuleNotFoundError i el parany d'anomenar un fitxer propi random.py o json.py. En importar, el mòdul s'executa sencer una sola vegada, i Python dona a __name__ el valor "__main__" només si és el fitxer que has llançat: aquesta és l'explicació completa de l'if __name__ == "__main__": que fèiem servir des de 04-04, la guarda que separa «el que faig quan m'executen» del «que ofereixo quan m'importen». Diversos mòduls en una carpeta amb __init__.py formen un paquet, amb importacions absolutes (from tascafacil.model import Tasca) o relatives (from .model import Tasca), i amb un __init__.py que es pot quedar buit o exposar una interfície còmoda. La biblioteca estàndard porta ja resolt gairebé tot —math, random, datetime, os, pathlib, json, csv, sys, time, statistics, collections, functools—, i per a la resta hi ha PyPI amb pip install i requirements.txt, sempre dins del venv del projecte i sempre mirant què s'instal·la. Finalment, les importacions circulars no s'arreglen amb trucs: s'eviten fent que les dependències vagin en una sola direcció.

Amb això es tanca el mòdul 7 i el salt més gran del curs. Vas començar amb una llista de diccionaris fràgil, en la qual qualsevol tasca podia néixer sense completada o amb la prioritat escrita com a "Alta", i amb les funcions que hi operaven escampades per un fitxer. Ara hi ha una classe Tasca que garanteix al seu constructor que cap tasca invàlida no arriba a existir i que porta dins el seu propi comportament; una classe Agenda que encapsula la col·lecció, valida el que hi entra, protegeix la seva llista interna i sap desar-se i reconstruir-se des de JSON; herència i polimorfisme en la seva forma justa, amb l'advertiment de preferir la composició; i un projecte repartit en el paquet tascafacil/, amb cada mòdul al seu lloc i les dependències apuntant en una sola direcció. TascaFàcil és la v0.17 i s'executa amb python -m tascafacil.

I tanmateix, mira-t'ho amb ulls de programador professional: aquell codi no està documentat més enllà d'uns docstrings solts, i ningú de fora no sabria per on entrar; no gestiona els errors, així que un fitxer JSON corrupte o un dies que arriba com a text encara tomben el programa sencer amb un traceback; no té historial, de manera que un canvi desafortunat es perd sense marxa enrere i no hi ha manera de saber qui va tocar què; i no té proves, així que cada modificació obliga a repassar el menú a mà pregant per no haver trencat res. Al mòdul 8 es resolen les quatre coses, començant per Documentació i comentaris: docstrings de veritat, comentaris que expliquen el perquè i no el què, i un README que permeti a qualsevol —inclòs tu d'aquí a sis mesos— entendre el projecte en cinc minuts.

© Copyright 2026. Tots els drets reservats