Introduzione
Quando un progetto Python cresce, la gestione degli import diventa un aspetto fondamentale. All’inizio può sembrare semplice scrivere import modulo e andare avanti, ma con più file, sottopacchetti e funzionalità distribuite, è facile creare codice difficile da mantenere. Un tema molto utile, spesso sottovalutato, è l’uso degli import relativi all’interno di un pacchetto.
Gli import relativi permettono ai moduli di un pacchetto di referenziarsi tra loro in modo coerente, senza dipendere troppo dalla struttura assoluta del progetto. Questo è particolarmente utile quando stai costruendo librerie, applicazioni modulari o progetti con più componenti interne.
In questo tutorial vedremo come funzionano gli import relativi, quando usarli, quali errori evitare e come organizzare il codice in modo pulito. L’obiettivo è capire non solo la sintassi, ma anche la logica architetturale che rende un progetto più robusto.
Codice completo
Prendiamo come esempio un piccolo pacchetto chiamato shop, con una struttura semplice ma realistica:
shop/
├── __init__.py
├── main.py
├── cart.py
└── utils/
├── __init__.py
└── pricing.py Ecco un esempio completo di codice che usa import relativi in modo corretto:
# shop/cart.py
from .utils.pricing import apply_discount
class Cart:
def __init__(self):
self.items = []
def add_item(self, name, price):
self.items.append({"name": name, "price": price})
def total(self):
subtotal = sum(item["price"] for item in self.items)
return apply_discount(subtotal, discount=0.10) # shop/utils/pricing.py
def apply_discount(amount, discount=0.0):
if not 0 <= discount <= 1:
raise ValueError("Lo sconto deve essere compreso tra 0 e 1")
return amount * (1 - discount) # shop/main.py
from .cart import Cart
def run():
cart = Cart()
cart.add_item("Tastiera meccanica", 120.0)
cart.add_item("Mouse ergonomico", 45.0)
print(f"Totale scontato: {cart.total():.2f} €")
if __name__ == "__main__":
run() # shop/__init__.py
# File presente per indicare che shop è un pacchetto Python.
# Può restare vuoto oppure esportare simboli pubblici. # shop/utils/__init__.py
# Sottopacchetto utilities.
# Anche questo file può essere vuoto. Per eseguire correttamente il progetto, il modo migliore è avviarlo come modulo:
python -m shop.main Spiegazione
Vediamo cosa succede in questo esempio.
1. Cos’è un import relativo
Un import relativo usa il punto . per indicare il pacchetto corrente o un livello superiore. Nel nostro esempio:
- from .utils.pricing import apply_discount significa: importa
apply_discountdal sottopacchettoutilsdel pacchetto corrente. - from .cart import Cart significa: importa
Cartdal modulocart.pydello stesso pacchetto.
Questo approccio è utile perché rende i riferimenti interni più stabili. Se in futuro sposti il pacchetto in un altro namespace, gli import interni restano coerenti, purché la struttura relativa rimanga invariata.
2. Perché servono i file __init__.py
I file __init__.py segnalano a Python che una cartella deve essere trattata come pacchetto. Nelle versioni moderne di Python, alcune strutture possono funzionare anche senza, ma includerli resta una best practice molto diffusa, soprattutto nei progetti strutturati e nelle librerie.
Inoltre, __init__.py può essere usato per:
- esporre API pubbliche del pacchetto;
- semplificare gli import dall’esterno;
- inizializzare componenti del pacchetto in modo controllato.
3. Il problema degli import eseguiti direttamente
Un errore comune è lanciare un file interno del pacchetto con:
python shop/main.py In questo caso, gli import relativi possono fallire con un errore simile a:
ImportError: attempted relative import with no known parent package Il motivo è semplice: eseguendo il file direttamente, Python non lo interpreta nel contesto del pacchetto shop. Per questo è meglio usare:
python -m shop.main In questo modo il modulo viene eseguito come parte del pacchetto e gli import relativi funzionano correttamente.
4. Quando usare gli import relativi
Gli import relativi sono una buona scelta quando:
- stai lavorando dentro un pacchetto ben definito;
- i moduli collaborano strettamente tra loro;
- vuoi evitare dipendenze troppo rigide dai percorsi assoluti interni;
- stai costruendo una libreria o un’app modulare con struttura stabile.
Invece, sono meno indicati in script isolati o in codice che deve essere eseguito fuori dal contesto del pacchetto.
5. Relativi vs assoluti
Confrontiamo rapidamente i due approcci:
- Import assoluto:
from shop.utils.pricing import apply_discount - Import relativo:
from .utils.pricing import apply_discount
L’import assoluto è molto leggibile e spesso preferibile nei punti di ingresso dell’applicazione. L’import relativo è invece comodo per il codice interno del pacchetto. Una regola pratica è questa:
- all’esterno del pacchetto: usa import assoluti;
- all’interno del pacchetto: valuta import relativi per mantenere il codice coeso.
Best practice
Per usare gli import relativi in modo professionale, tieni presenti queste linee guida.
- Usa import relativi solo dentro pacchetti veri. Se il codice non è organizzato come pacchetto, i relativi non sono la scelta giusta.
- Esegui i moduli con
python -mquando devono usare import relativi. - Evita di mischiare troppo import assoluti e relativi nello stesso progetto senza una regola chiara.
- Non abusare dei “punti”: import come
from ...utils import xdiventano poco leggibili se la struttura è troppo profonda. In quel caso, rivedi l’architettura. - Separa bene entry point e logica interna. Il file che avvia l’applicazione può usare import assoluti, mentre i moduli interni possono usare import relativi.
- Documenta la struttura del pacchetto, soprattutto se il progetto è condiviso con altri sviluppatori.
Un’altra buona pratica è mantenere i moduli piccoli e con responsabilità chiare. Se un modulo fa troppo, gli import diventano più complessi e aumenta il rischio di dipendenze confuse.
Infine, quando un progetto cresce, valuta se creare un livello di API pubblica nel file __init__.py. Questo può semplificare l’uso del pacchetto dall’esterno, lasciando invece gli import relativi per i dettagli interni.
Riepilogo
Gli import relativi sono uno strumento molto utile per organizzare meglio i moduli di un pacchetto Python. Permettono di collegare tra loro i componenti interni in modo pulito, leggibile e meno dipendente dalla posizione assoluta nel progetto.
In questo tutorial abbiamo visto:
- come si scrivono gli import relativi;
- perché i file
__init__.pysono importanti; - perché non conviene eseguire direttamente un file interno del pacchetto;
- quando preferire import assoluti o relativi;
- quali abitudini adottare per mantenere il progetto ordinato.
Se lavori su applicazioni modulari o librerie, padroneggiare gli import relativi ti aiuterà a scrivere codice più mantenibile e professionale.
Approfondisci con risorse ufficiali
- Python Docs - The import system: documentazione ufficiale sul sistema di import di Python.
- Python Docs - Packages: guida ai pacchetti e ai moduli.
- Python Docs - Executing modules as scripts: spiegazione dell’uso di
python -m. - PEP 328: introduce gli import assoluti e relativi in Python.
- PEP 420: namespace packages, utile per capire alcune evoluzioni del sistema dei pacchetti.
