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

  1. Què és un mòdul (i per què ja fas servir mòduls)
  2. import, from ... import i àlies amb as
  3. Crear papyrus_utils.py, el mòdul de la botiga
  4. if __name__ == "__main__": mòdul i script alhora
  5. Paquets: carpetes amb __init__.py
  6. 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

print(math.sqrt(16))    # 4.0
print(math.pi)          # 3.141592653589793

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 import van 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 None

Observa dues decisions de disseny:

  1. El mòdul també té docstring: la primera cadena del fitxer documenta el mòdul sencer (help(papyrus_utils) la mostra).
  2. cercar_llibre() ha canviat de signatura: a la lliçó 03-01 llegia la llista global cataleg de menu.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:

from papyrus_utils import preu_final, cercar_llibre, NOM_BOTIGA

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')}")   # 2
flowchart 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__.py pot 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:

  1. La carpeta de l'script que estàs executant — per això menu.py troba papyrus_utils.py: són veïns.
  2. Les carpetes de la biblioteca estàndard — allà viuen math, random, datetime.
  3. El site-packages de l'entorn actiu — allà instal·la pip; amb el .venv activat, és el site-packages del 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:

import sys
print(sys.path)     # la llista de carpetes, en ordre de cerca

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.py de proves i import math carregarà 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 .py no és al costat de l'script. Comprova on ets (pwd a la terminal) i on és el fitxer.
  • Eclipsar mòduls estàndard: un random.py propi trenca import random a 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'import fa servir el nom del mòdul, sense extensió. Amb .py obtens un error de mòdul py inexistent.
  • Codi solt al mòdul: un print() fora de funcions i fora del bloc __main__ s'executarà a cada primer import, 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

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