El diagnòstic en tancar la lliçó anterior era clar: preu_final() està copiada a tarifa.py i a altres scripts, cercar_llibre() viu tancada a menu.py, i cada còpia és una font d'inconsistències — el dia que l'Anna canviï l'arrodoniment, en quants llocs caldrà tocar-ho? La solució és el mòdul: un fitxer .py les funcions i constants del qual poden importar-se des de qualsevol altre fitxer. De fet, ja consumeixes mòduls sense saber-ho cada vegada que instal·laves paquets amb pip (lliçó 01-06); avui aprendràs l'altra meitat: crear els teus. Construirem papyrus_utils.py, el primer mòdul propi de la botiga, entendràs les variants d'import, l'idioma if __name__ == "__main__", què és un paquet amb __init__.py i on busca Python els mòduls que li demanes.
Contingut
- Què és un mòdul (i per què ja fas servir mòduls)
import,from ... importi àlies ambas- Crear
papyrus_utils.py, el mòdul de la botiga if __name__ == "__main__": mòdul i script alhora- Paquets: carpetes amb
__init__.py - On busca Python els mòduls:
sys.path
Què és un mòdul (i per què ja fas servir mòduls)
Un mòdul és, simplement, un fitxer .py. Tot el que defineix al seu nivell superior — funcions, constants, variables — pot posar-se a disposició d'altres fitxers mitjançant import. Els mòduls venen de tres orígens:
| Origen | Exemples | Cal instal·lar-los? |
|---|---|---|
| Biblioteca estàndard (venen amb Python) | math, random, datetime |
No |
De tercers (s'instal·len amb pip al .venv) |
requests, pandas |
Sí (lliçó 01-06) |
| Propis (els escrius tu) | papyrus_utils |
No: n'hi ha prou amb un fitxer .py |
La mecànica d'importació és idèntica per als tres. Comencem amb un d'estàndard per veure la sintaxi en terreny conegut:
import math carrega el mòdul i el deixa disponible sota el seu nom; a partir d'aquí, tot el que és seu es fa servir amb la notació de punt: math.sqrt, math.pi. El nom del mòdul actua de "cognom" que evita col·lisions amb les teves pròpies variables.
import, from ... import i àlies amb as
Hi ha tres variants principals, cadascuna amb el seu cas d'ús:
# 1) Importar el modul sencer (es fa servir amb prefix)
import math
math.sqrt(16)
# 2) Importar noms concrets (es fan servir sense prefix)
from math import sqrt, pi
sqrt(16)
# 3) Alies: rebatejar el modul (o el nom) en importar-lo
import datetime as dt
dt.date.today()| Variant | Avantatge | Inconvenient | Quan fer-la servir |
|---|---|---|---|
import modul |
Queda clar d'on ve cada cosa | Més teclejar (modul.funcio) |
Per defecte; sempre que dubtis |
from modul import nom |
Crides curtes | Es perd el "cognom"; risc de col·lisió de noms | Pocs noms, molt usats al fitxer |
import modul as alies |
Abreuja mòduls de nom llarg | Un àlies capriciós despista | Àlies consolidats per convenció (numpy as np, mòdul 11) |
Existeix una quarta forma, from modul import * (importar-ho tot sense prefix), que has de conèixer per no fer-la servir: t'omple el fitxer de noms invisibles — no saps què has importat ni què has trepitjat — i PEP 8 la reserva per a casos molt comptats.
Dues convencions importants de PEP 8:
- Els
importvan al principi del fitxer, després de la docstring del mòdul, un per línia. - Ordre en tres blocs separats per línia en blanc: biblioteca estàndard → paquets de tercers → mòduls propis.
Crear papyrus_utils.py, el mòdul de la botiga
Mans a l'obra. A la carpeta papyrus (al costat de menu.py), crea papyrus_utils.py amb les peces que anàvem copiant d'script en script:
"""Utilitats compartides de la llibreria Papyrus.
Constants de la botiga i funcions de preus i cerca
usades per menu.py, tiquet.py i venda_soci.py.
"""
NOM_BOTIGA = "Papyrus"
IVA_LLIBRES = 0.04
DESCOMPTE_SOCI = 0.05
def preu_final(base, soci=False, iva=IVA_LLIBRES):
"""Retorna el preu final: descompte de soci (si escau) i despres l'IVA."""
if soci:
base = base * (1 - DESCOMPTE_SOCI)
return round(base * (1 + iva), 2)
def cercar_llibre(cataleg, cercat):
"""Retorna l'index del titol dins del cataleg, o None si no hi es.
La comparacio ignora majuscules i minuscules.
"""
for i, titol in enumerate(cataleg):
if titol.lower() == cercat.lower():
return i
return NoneObserva dues decisions de disseny:
- El mòdul també té docstring: la primera cadena del fitxer documenta el mòdul sencer (
help(papyrus_utils)la mostra). cercar_llibre()ha canviat de signatura: a la lliçó 03-01 llegia la llista globalcatalegdemenu.py; això funcionava perquè funció i llista vivien al mateix fitxer. Un mòdul no pot (ni ha de) dependre de les variables de qui l'importa, així que ara rep el catàleg com a paràmetre — la lliçó de "rep dades, no llegeixis globals" (03-01) aplicada de debò. Les constants (IVA_LLIBRES,DESCOMPTE_SOCI) sí que viuen al mòdul: són de la botiga, no d'un script concret.
I ara, la recompensa. Qualsevol script de la carpeta pot escriure:
import papyrus_utils
cataleg = ["L'Odissea", "Hamlet", "El Quixot"]
preus = [12.50, 9.95, 15.90]
index = papyrus_utils.cercar_llibre(cataleg, "hamlet")
if index is not None:
preu_lluis = papyrus_utils.preu_final(preus[index], soci=True)
print(f"{cataleg[index]} per a Lluis: {preu_lluis} €") # Hamlet per a Lluis: 9.83 €O, si prefereix noms curts:
Una sola definició, tants usuaris com calgui. El dia que canviï l'IVA, s'edita una línia d'un fitxer i tots els scripts queden actualitzats. A la propera lliçó reescriurem menu.py sobre aquest mòdul.
Un detall tècnic útil: Python executa el fitxer del mòdul una sola vegada, en el primer import del programa; els següents import reutilitzen el mòdul ja carregat. Per això importar és barat i per això no has de posar "codi de treball" solt en un mòdul... cosa que ens porta a l'apartat següent.
if __name__ == "__main__": mòdul i script alhora
En importar, Python executa el fitxer sencer: els def defineixen funcions (inofensiu), però un print() o un input() solts s'executarien també, colant-se al programa de qui importa. Com tenir un mòdul que a més es pugui executar sol — per exemple, per provar-lo? Python assigna a cada fitxer una variable automàtica, __name__:
- Si el fitxer s'executa directament (
python papyrus_utils.py),__name__val"__main__". - Si el fitxer s'importa,
__name__val el seu nom de mòdul ("papyrus_utils").
L'idioma estàndard aprofita aquesta diferència. Afegeix al final de papyrus_utils.py:
if __name__ == "__main__":
# Petita demostracio: nomes corre en executar el fitxer directament.
print(f"Modul d'utilitats de {NOM_BOTIGA}")
print(f"Hamlet, soci: {preu_final(9.95, soci=True)} €") # 9.83 €
demo = ["L'Odissea", "Hamlet", "El Quixot"]
print(f"Index de 'el quixot': {cercar_llibre(demo, 'el quixot')}") # 2flowchart TD
A[Python executa papyrus_utils.py] --> B{"__name__?"}
B -->|"python papyrus_utils.py<br/>__name__ == '__main__'"| C[Corre la demo/les proves]
B -->|"import papyrus_utils<br/>__name__ == 'papyrus_utils'"| D[Només defineix funcions i constants:<br/>silenci absolut]
Resultat: python papyrus_utils.py imprimeix la demostració; import papyrus_utils des de menu.py no imprimeix res. Acostuma't a tancar així també els teus scripts "principals": és la marca de fàbrica del codi Python ben educat, i al mòdul 9 aquest bloc serà el trampolí natural de les proves.
Paquets: carpetes amb __init__.py
Quan els mòduls es multipliquen, s'agrupen en paquets: carpetes que contenen mòduls i un fitxer especial __init__.py (habitualment buit) que diu a Python "aquesta carpeta és un paquet". Si Papyrus continua creixent, el seu projecte podria organitzar-se així:
papyrus/ <- carpeta del projecte (amb el seu .venv)
menu.py <- script principal
papyrus_utils.py <- el nostre modul (etapa actual)
botiga/ <- un PAQUET (etapa futura)
__init__.py <- marca la carpeta com a paquet
preus.py <- modul: preu_final, IVA_LLIBRES...
inventari.py <- modul: cercar_llibre, estocs...La notació de punt travessa els nivells:
import botiga.preus
botiga.preus.preu_final(9.95, soci=True)
from botiga.inventari import cercar_llibre
cercar_llibre(cataleg, "Faust")__init__.pypot estar buit; també pot executar codi d'inicialització del paquet o reexportar noms còmodes — de moment, buit és perfecte.- Els paquets poden imbricar-se (
botiga.informes.mensual), formant l'arbre que ja has vist en biblioteques de tercers.
Per a la mida actual de Papyrus, un únic papyrus_utils.py pla és la decisió correcta — crear paquets prematurament és burocràcia. Però ara, quan als mòduls 10 i 11 et trobis amb from flask import Flask o import pandas as pd, sabràs exactament quina estructura hi ha al darrere: carpetes amb __init__.py i fitxers .py.
On busca Python els mòduls: sys.path
Quan escrius import papyrus_utils, com troba Python el fitxer? Recorre, en ordre, una llista de carpetes anomenada sys.path:
- La carpeta de l'script que estàs executant — per això
menu.pytrobapapyrus_utils.py: són veïns. - Les carpetes de la biblioteca estàndard — allà viuen
math,random,datetime. - El
site-packagesde l'entorn actiu — allà instal·la pip; amb el.venvactivat, és elsite-packagesdel projecte (lliçó 01-06, ara amb sentit complet).
Fa servir el primer mòdul que troba i, si esgota la llista, llança ModuleNotFoundError. Pots inspeccionar la llista tu mateix:
Dues conseqüències pràctiques de l'ordre de cerca:
- Si el teu script i el teu mòdul són a la mateixa carpeta, tot funciona sense configurar res. És el nostre cas i el de la majoria de projectes petits.
- Com que la teva carpeta va primera, un fitxer teu anomenat com un mòdul estàndard l'eclipsa: crea un
math.pyde proves iimport mathcarregarà el teu, trencant coses amb errors desconcertants. No anomenis mai els teus fitxers com mòduls coneguts (random.py,string.py,test.py...).
Errors Comuns i Consells
ModuleNotFoundError: No module named 'papyrus_utils': gairebé sempre vol dir que executes l'script des d'una altra carpeta o que el.pyno és al costat de l'script. Comprova on ets (pwda la terminal) i on és el fitxer.- Eclipsar mòduls estàndard: un
random.pypropi trencaimport randoma tota la carpeta. Si un mòdul estàndard "es comporta estrany", busca fitxers teus amb el seu nom. - Escriure
import papyrus_utils.py: l'importfa servir el nom del mòdul, sense extensió. Amb.pyobtens un error de mòdulpyinexistent. - Codi solt al mòdul: un
print()fora de funcions i fora del bloc__main__s'executarà a cada primerimport, embrutant qui importa. Tot el que sigui executable, dins de funcions o del bloc__main__. from modul import *: contamina el teu espai de noms i fa impossible saber d'on ha sortit cada funció. Importa noms concrets o el mòdul sencer.- Modificar el mòdul i no veure els canvis al REPL: el REPL desa en memòria cau els mòduls importats; reinicia'l després d'editar el fitxer (els scripts no pateixen això: cada execució parteix de zero).
- Consell: pregunta clau abans de moure una funció a un mòdul: "la necessiten dos o més scripts, o podria necessitar-la demà?". Si sí, al mòdul. Constants del negoci (
IVA_LLIBRES) incloses.
Exercicis
Exercici 1: venda_soci.py reneix sobre el mòdul
Reescriu venda_soci.py perquè no defineixi cap funció ni constant pròpia: ha d'importar de papyrus_utils el que calgui, demanar amb input() un títol, cercar-lo a cataleg = ["L'Odissea", "Hamlet", "El Quixot"] (amb preus = [12.50, 9.95, 15.90]), i imprimir el preu de soci d'en Lluís o '<titol>' no es al cataleg. si no existeix. Fes servir la variant from ... import.
Exercici 2: el mòdul autoexecutable
Afegeix a papyrus_utils.py la funció es_soci_valid(codi) que retorni True si codi és una cadena d'exactament 4 dígits (pista: len() i el mètode isdigit() que coneixes del mòdul 2). Amplia el bloc if __name__ == "__main__" amb tres comprovacions que imprimeixin el resultat de es_soci_valid("0042"), es_soci_valid("42") i es_soci_valid("ABCD"). Verifica que python papyrus_utils.py mostra les comprovacions i que importar-lo des d'un altre script no mostra res.
Exercici 3: anatomia d'un paquet
Sense escriure codi, respon raonadament: (a) què converteix una carpeta corrent en un paquet?; (b) si Papyrus tingués l'estructura botiga/preus.py amb __init__.py, quines dues formes d'import permetrien cridar preu_final?; (c) en quin ordre buscarà Python el mòdul botiga quan menu.py faci import botiga, i per què el troba?
Solucions
Exercici 1:
from papyrus_utils import preu_final, cercar_llibre
cataleg = ["L'Odissea", "Hamlet", "El Quixot"]
preus = [12.50, 9.95, 15.90]
cercat = input("Titol per a Lluis: ")
index = cercar_llibre(cataleg, cercat)
if index is None:
print(f"'{cercat}' no es al cataleg.")
else:
print(f"'{cataleg[index]}' per a Lluis: {preu_final(preus[index], soci=True)} €")Tot el coneixement del negoci (IVA, descompte, com comparar títols) viu al mòdul; l'script només orquestra entrada, crida i sortida. Prova amb hamlet: 'Hamlet' per a Lluis: 9.83 €.
Exercici 2:
def es_soci_valid(codi):
"""Retorna True si el codi de soci son exactament 4 digits."""
return len(codi) == 4 and codi.isdigit()
if __name__ == "__main__":
print(f"Modul d'utilitats de {NOM_BOTIGA}")
print(f"Hamlet, soci: {preu_final(9.95, soci=True)} €")
print(es_soci_valid("0042")) # True
print(es_soci_valid("42")) # False (longitud 2)
print(es_soci_valid("ABCD")) # False (no son digits)L'and fa curtcircuit (mòdul 2): si la longitud no és 4, ni tan sols avalua isdigit(). En importar el mòdul des d'un altre fitxer, el bloc __main__ queda en silenci perquè __name__ val "papyrus_utils".
Exercici 3: (a) Contenir un fitxer __init__.py (encara que estigui buit). (b) import botiga.preus i cridar botiga.preus.preu_final(...), o from botiga.preus import preu_final i cridar preu_final(...) directament. (c) Python recorre sys.path en ordre: primer la carpeta de l'script (papyrus/), on troba la carpeta botiga amb el seu __init__.py; no arriba a mirar la biblioteca estàndard ni site-packages.
Conclusió
Papyrus té per fi una caixa d'eines central: papyrus_utils.py, amb les constants de la botiga, preu_final(base, soci=False, iva=IVA_LLIBRES) i una cercar_llibre(cataleg, cercat) que ja no depèn de globals alienes — i amb un bloc if __name__ == "__main__" que la fa provable amb només executar-la. Saps triar entre import modul, from modul import nom i els àlies as, saps què és un paquet (__init__.py) i entens el mapa de carpetes que recorre sys.path per resoldre cada import. Aquest mateix mecanisme obre una porta enorme: la biblioteca estàndard, desenes de mòduls ja escrits, provats i documentats que venen amb Python — matemàtiques, atzar, dates, sistema... A la propera lliçó farem una visita guiada pels més útils i tancarem el mòdul reescrivint menu.py com es mereix: important papyrus_utils.
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
