Python: import relativi e organizzazione pulita dei pacchetti

by theArchitect
SHARE
Python: import relativi e organizzazione pulita dei pacchetti
© Guida-HTML5.it

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_discount dal sottopacchetto utils del pacchetto corrente.
  • from .cart import Cart significa: importa Cart dal modulo cart.py dello 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 -m quando 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 x diventano 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__.py sono 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.

SHARE