Python - Ereditarietà

Cos'è l'Ereditarietà in Python?

L'ereditarietà è un concetto fondamentale della programmazione orientata agli oggetti (OOP) che consente a una classe di ereditare le proprietà e i metodi di un'altra classe. Questo permette la riutilizzabilità e la modularità del codice, poiché è possibile creare nuove classi basate su quelle esistenti senza dover riscrivere lo stesso codice. In Python, l'ereditarietà viene implementata utilizzando la parola chiave class seguita dal nome della classe genitore tra parentesi.

Python - Inheritance

Creazione di una Classe Genitore

Iniziamo creando una semplice classe genitore chiamata Animal:

class Animal:
def __init__(self, name):
self.name = name

def speak(self):
raise NotImplementedError("La sottoclasse deve implementare questo metodo")

In questo esempio, definiamo una classe Animal con un metodo __init__ che inizializza l'attributo name e un metodo speak che solleva un NotImplementedError. Questo errore verrà sollevato se la sottoclasse non fornisce la propria implementazione del metodo speak.

Creazione di una Classe Figlia

Ora, creiamo una classe figlia chiamata Dog che eredita dalla classe Animal:

class Dog(Animal):
def speak(self):
return f"{self.name} dice Woof!"

La classe Dog eredita tutte le proprietà e i metodi della classe Animal, ma fornisce la propria implementazione del metodo speak. Quando creiamo un'istanza della classe Dog e chiamiamo il suo metodo speak, restituirà "[nome del cane] dice Woof!" invece di sollevare un errore.

my_dog = Dog("Buddy")
print(my_dog.speak())  # Output: Buddy dice Woof!

Tipi di Ereditarietà

Ci sono tre tipi di ereditarietà in Python: ereditarietà singola, ereditarietà multipla, ereditarietà a più livelli e ereditarietà gerarchica. Esploreremo ciascun tipo in dettaglio.

Python - Ereditarietà Singola

L'ereditarietà singola è la forma più comune di ereditarietà, in cui una classe eredita da una sola classe genitore. Come abbiamo visto sopra, la classe Dog eredita dalla classe Animal, che è un esempio di ereditarietà singola.

Python - Ereditarietà Multipla

L'ereditarietà multipla è quando una classe eredita da più di una classe genitore. Tuttavia, Python non supporta direttamente l'ereditarietà multipla attraverso la sintassi della definizione della classe. Invece, utilizza una tecnica chiamata "mixins" o "interfacce" per ottenere l'ereditarietà multipla.

Ordine di Risoluzione dei Metodi (MRO)

Python utilizza l'algoritmo di linearizzazione C3 per determinare l'ordine in cui vengono cercate le classi base quando si cerca un metodo. L'MRO garantisce che ogni classe appaia al massimo una volta nell'ordine di risoluzione dei metodi e mantiene l'ordine delle classi base come sono state specificate nella definizione della classe.

Python - Ereditarietà a Più Livelli

L'ereditarietà a più livelli si verifica quando una classe eredita da una sottoclasse, formando una gerarchia di classi. Ecco un esempio:

class Mammal(Animal):
pass

class Cat(Mammal):
def speak(self):
return f"{self.name} dice Meow!"

In questo caso, la classe Cat eredita dalla classe Mammal, che a sua volta eredita dalla classe Animal. Questo forma una gerarchia di ereditarietà a più livelli.

Python - Ereditarietà Gerarchica

L'ereditarietà gerarchica coinvolge più sottoclassi che ereditano da una singola superclasse. Per esempio:

class Reptile(Animal):
pass

class Snake(Reptile):
def speak(self):
return f"{self.name} dice Sss!"

class Lizard(Reptile):
def speak(self):
return f"{self.name} dice Crawl!"

In questo caso, sia la classe Snake che la classe Lizard ereditano dalla classe Reptile, che a sua volta eredita dalla classe Animal. Questo forma una struttura di ereditarietà gerarchica.

Python - Ereditarietà Ibrida

L'ereditarietà ibrida combina caratteristiche di ereditarietà multipla e altri tipi di ereditarietà. Non è direttamente supportata in Python, ma può essere ottenuta utilizzando mixins o combinando diversi tipi di ereditarietà.

La funzione super()

La funzione super() viene utilizzata per chiamare metodi dalla classe genitore all'interno della sottoclasse. È particolarmente utile quando si vuole estendere o sovrascrivere un metodo della classe genitore mantenendo ancora parte della sua funzionalità. Ecco un esempio:

class Bird(Animal):
def __init__(self, name, wingspan):
super().__init__(name)
self.wingspan = wingspan

def speak(self):
return f"{self.name} dice Cip!"

In questo caso, la classe Bird eredita dalla classe Animal e utilizza la funzione super() per chiamare il metodo __init__ della classe genitore. Questo assicura che l'attributo name venga inizializzato correttamente. Inoltre, la classe Bird fornisce la propria implementazione del metodo speak.

my_bird = Bird("Tweety", 10)
print(my_bird.speak())  # Output: Tweety dice Cip!

Utilizzando l'ereditarietà, è possibile creare classi più specializzate che ereditano ed estendono la funzionalità delle loro classi genitore, promuovendo la riutilizzabilità e l'organizzazione del codice. Ricorda che l'ereditarietà è solo un aspetto dell'OOP, e ci sono altri concetti come la polimorfismo, l'incapsulamento e l'astrazione che anche giocano un ruolo cruciale nella progettazione di sistemi software robusti e manutenibili.

Credits: Image by storyset