Classi Python: quando l'ereditarietà non è una questione di patrimonio

 

Ah, la programmazione orientata agli oggetti in Python! Quel meraviglioso mondo dove tutto è un oggetto, persino il nulla (sì, pure None è un oggetto, MIND BLOWN!). Ma parliamo di classi, quelle strutture magiche che ci permettono di organizzare il codice come se fossimo degli architetti digitali con la sindrome di Howard Roark.

Prima di tutto, una classe in Python è come una ricetta di cucina: hai gli ingredienti (attributi) e le istruzioni per usarli (metodi). Ma a differenza della cucina di mia nonna, qui puoi anche ereditare le ricette e modificarle senza che nessuno si offenda a morte.

class Pizza: def __init__(self): self.ingredienti = ["pomodoro", "mozzarella"] self.cottura = "forno a legna" def sforna(self): return "Pizza margherita pronta!" class PizzaHawaiiana(Pizza): # Gli italiani stanno già urlando def __init__(self): super().__init__() self.ingredienti.append("ananas") # SACRILEGIO!

E qui arriva il bello dell'ereditarietà: è come avere un figlio che prende il meglio (si spera) dai genitori, ma può anche sviluppare una sua personalità. ATTENZIONE però: l'ereditarietà è come il peperoncino, va usata con moderazione!

Quando mi piace ereditare? Ecco alcuni casi d'uso che farebbero annuire persino Guido van Rossum:

  1. Quando hai una gerarchia naturale di oggetti (Animal -> Mammal -> Dog)
  2. Quando vuoi estendere funzionalità senza modificare il codice originale
  3. Quando vuoi riutilizzare codice senza copincollare come un barbaro

Ma per l'amor del cielo, NON ereditare quando:

  • La tua gerarchia di classi assomiglia all'albero genealogico dei Targaryen
  • Stai cercando di forzare relazioni tra oggetti che hanno in comune quanto un pinguino e una motocicletta
  • Ti ritrovi a scrivere più codice per gestire l'ereditarietà che per risolvere il problema originale

E parliamo del famoso super().__init__(), quella chiamata magica che fa felici i genitori (classi base) e mantiene la pace in famiglia. Dimenticarlo è come non chiamare tua madre la domenica: tecnicamente puoi farlo, ma le conseguenze potrebbero essere devastanti.

class DisastroImminente: def __init__(self): self.problemi = ["memory leak", "caffè freddo"] class DisastroTotale(DisastroImminente): def __init__(self): # Ops, mi sono dimenticato super().__init__() self.problemi.append("deadline mancata") # Indovina un po'? self.problemi non esiste!

E che dire delle classi astratte? Sono come quei parenti che danno un sacco di consigli ma non fanno mai niente di concreto. Utilissime per definire interfacce, ma se provi a istanziarle direttamente ti guardano male e lanciano eccezioni.

from abc import ABC, abstractmethod class DevelopmentProcess(ABC): @abstractmethod def write_code(self): pass # "Qualcun altro lo implementerà" @abstractmethod def fix_bugs(self): pass # "Non sarò io a farlo"

Il mio consiglio personale? Usa l'ereditarietà come useresti i GOTO in Assembly: solo quando serve davvero. La composizione (avere oggetti come attributi) è spesso una soluzione più pulita e flessibile. È come avere amici invece che parenti: puoi sceglierli e cambiarli quando vuoi!

TL;DR per i dev con deficit di attenzione: "L'ereditarietà in Python è potente ma usala con saggezza, altrimenti il tuo codice diventerà più ingarbugliato della trama di Lost."

PS: Se ti ritrovi con più di tre livelli di ereditarietà, fermati, respira e chiediti: "Cosa sto facendo della mia vita?"

PPS: E ricorda sempre: in Python, tutti gli oggetti sono uguali, ma alcuni sono più uguali degli altri (cit. non richiesta a George Orwell).

PPPS: Se questo articolo ti è sembrato lungo, pensa a quanto è lungo debuggare una catena di ereditarietà mal progettata.

Post più popolari