Overview

  • Impara l’Ereditarietà nella Programmazione Orientata agli Oggetti e le varie forme di Ereditarietà
  • Capire l’Overriding dei Metodi e la funzione super() nel mondo della Programmazione Orientata agli Oggetti

Introduzione

L’Ereditarietà è uno degli aspetti più importanti della Programmazione Orientata agli Oggetti (OOP). La chiave per capire l’Ereditarietà è che fornisce la riusabilità del codice. Invece di scrivere lo stesso codice, ancora e ancora, possiamo semplicemente ereditare le proprietà di una classe nell’altra.

Questo, come potete immaginare, fa risparmiare un sacco di tempo. E il tempo è denaro nella scienza dei dati!

L'ereditarietà nella programmazione orientata agli oggetti

OOP è tutto su oggetti del mondo reale e l’ereditarietà è un modo di rappresentare relazioni del mondo reale. Ecco un esempio – auto, bus, bici – tutti questi rientrano in una categoria più ampia chiamata Veicolo. Questo significa che hanno ereditato le proprietà della classe Vehicle, cioè sono tutti usati per il trasporto.

Possiamo rappresentare questa relazione nel codice con l’aiuto dell’ereditarietà.

Un’altra cosa intrigante dell’ereditarietà è che è di natura transitiva. Ma cosa significa questo? Lo vedremo in dettaglio più avanti in questo articolo. Python supporta anche vari tipi di ereditarietà che tratterò in dettaglio in questo articolo.

Questo è il secondo articolo della serie di articoli relativi alla programmazione orientata agli oggetti. Si prega di leggere anche il primo articolo:

  • Concetti di base della programmazione orientata agli oggetti.

Tabella dei contenuti

  1. Che cos’è l’ereditarietà nella programmazione orientata agli oggetti?
  2. Diverse forme di ereditarietà nella programmazione orientata agli oggetti
    • Ereditarietà singola
    • Ereditarietà multipla
    • Ereditarietà a piùlivello
    • Ereditarietà gerarchica
    • Ereditarietà ibrida
  3. Sovrapposizione di metodi
  4. La funzione super()

Che cos’è l’ereditarietà nella programmazione orientata agli oggetti?

L’ereditarietà è la procedura in cui una classe eredita gli attributi e i metodi di un’altra classe. La classe di cui si ereditano le proprietà e i metodi è conosciuta come la classe genitore. E la classe che eredita le proprietà dalla classe genitore è la classe figlia.

La cosa interessante è che, insieme alle proprietà e ai metodi ereditati, una classe figlia può avere le proprie proprietà e metodi.

Potete usare la seguente sintassi:\per implementare l’ereditarietà in Python:

class parent_class:body of parent classclass child_class( parent_class):body of child class

Vediamo l’implementazione:

class Car: #parent class def __init__(self, name, mileage): self.name = name self.mileage = mileage def description(self): return f"The {self.name} car gives the mileage of {self.mileage}km/l"class BMW(Car): #child class passclass Audi(Car): #child class def audi_desc(self): return "This is the description method of class Audi."
obj1 = BMW("BMW 7-series",39.53)print(obj1.description())obj2 = Audi("Audi A8 L",14)print(obj2.description())print(obj2.audi_desc())

Output:

L'ereditarietà nella programmazione orientata agli oggetti

Abbiamo creato due classi figlie “BMW” e “Audi” che hanno ereditato i metodi e le proprietà della classe madre “Car”. Non abbiamo fornito funzioni e metodi aggiuntivi nella classe BMW. Mentre c’è un metodo aggiuntivo nella classe Audi.

Vedete come il metodo di istanza description() della classe padre è accessibile dagli oggetti delle classi figlie con l’aiuto di obj1.description() e obj2.description(). E anche il metodo separato della classe Audi è accessibile usando obj2.audi_desc().

Possiamo controllare la classe base o padre di qualsiasi classe usando un attributo di classe incorporato __bases__

print(BMW.__bases__, Audi.__bases__)
Come possiamo vedere qui, la classe base di entrambe le sottoclassi è Car. Ora, vediamo cosa succede quando si usa __base__ con la classe madre Car:
print( Car.__bases__ )
Ereditarietà nella programmazione orientata agli oggetti - Stampa sottoclasse
Ogni volta che creiamo una nuova classe in Python 3.x, essa viene ereditata da una classe base integrata chiamata Object. In altre parole, la classe Object è la radice di tutte le classi.

Forme di ereditarietà nella programmazione orientata agli oggetti

Ci sono a grandi linee cinque forme di ereditarietà basate sul coinvolgimento delle classi padre e figlia.

1. Eredità singola

Questa è una forma di eredità in cui una classe eredita solo una classe genitore. Questa è la forma semplice di ereditarietà e quindi viene anche chiamata ereditarietà semplice.

class Parent: def f1(self): print("Function of parent class.")class Child(Parent): def f2(self): print("Function of child class.")object1 = Child()object1.f1()object1.f2()
Ereditarietà nella programmazione orientata agli oggetti - Ereditarietà singola
Qui la classe Child eredita solo una classe Parent, quindi questo è un esempio di ereditarietà singola.

2. Eredità multipla

Un’eredità diventa multipla quando una classe eredita più di una classe genitore. La classe figlio dopo aver ereditato le proprietà dalle varie classi padre ha accesso a tutti i loro oggetti.

class Parent_1: def f1(self): print("Function of parent_1 class.")class Parent_2: def f2(self): print("Function of parent_2 class.")class Parent_3: def f3(self): print("function of parent_3 class.")class Child(Parent_1, Parent_2, Parent_3): def f4(self): print("Function of child class.")object_1 = Child()object_1.f1()object_1.f2()object_1.f3()object_1.f4()
Eredità nella programmazione orientata agli oggetti - Eredità multipla

Qui abbiamo una classe figlio che sta ereditando le proprietà di tre classi padre_1, padre_2, e padre_3. Tutte le classi hanno funzioni diverse e tutte le funzioni sono chiamate usando l’oggetto della classe Child.

Ma supponiamo che una classe Child erediti due classi che hanno la stessa funzione:
class Parent_1: def f1(self): print("Function of parent_1 class.")class Parent_2: def f1(self): print("Function of parent_2 class.")class Child(Parent_1, Parent_2): def f2(self): print("Function of child class.")

Qui, le classi Parent_1 e Parent_2 hanno la stessa funzione f1(). Ora, quando l’oggetto della classe Bambino chiama f1(), dato che la classe Bambino sta ereditando entrambe le classi genitore, cosa pensate che dovrebbe succedere?

obj = Child() obj.f1()
L'ereditarietà nella programmazione orientata agli oggetti

Ma perché non è stata ereditata la funzione f1() della classe Genitore_2?

Nell’ereditarietà multipla, la classe figlio cerca prima il metodo nella propria classe. Se non lo trova, allora cerca nelle classi genitore in ordine di profondità_prima e sinistra-destra. Dato che questo era un esempio semplice con solo due classi genitore, possiamo vedere chiaramente che la classe Genitore_1 è stata ereditata per prima, quindi la classe figlio cercherà il metodo nella classe Genitore_1 prima di cercare nella classe Genitore_2.

Ma per problemi di ereditarietà complicati, diventa difficile identificare l’ordine. Quindi il modo attuale di fare questo si chiama Method Resolution Order (MRO) in Python. Possiamo trovare l’MRO di qualsiasi classe usando l’attributo __mro__

Child.__mro__
Ereditarietà nella programmazione orientata agli oggetti - MRO

Questo dice che la classe Child ha visitato prima la classe Parent_1 e poi Parent_2, quindi il metodo f1() di Parent_1 sarà chiamato.

Facciamo un esempio un po’ complicato in Python:

class Parent_1:passclass Parent_2:passclass Parent_3:passclass Child_1(Parent_1,Parent_2):passclass Child_2(Parent_2,Parent_3):passclass Child_3(Child_1,Child_2,Parent_3):pass

Qui, la classe Child_1 sta ereditando due classi – Parent_1 e Parent_2. Anche la classe Child_2 eredita due classi – Parent_2 e Parent_3. Un’altra classe Child_3 sta ereditando tre classi – Child_1, Child_2 e Parent_3.

Ora, solo guardando questa eredità, è abbastanza difficile determinare l’ordine di risoluzione del metodo per la classe Child_3. Quindi ecco l’uso effettivo di __mro__-

Child_3.__mro__

Possiamo vedere che prima l’interprete cerca Child_3, poi Child_1 seguito da Parent_1, Child_2, Parent_2 e Parent_3 rispettivamente.

3. Eredità multilivello

Per esempio, una classe_1 viene ereditata da una classe_2 e questa classe_2 viene anche ereditata da classe_3 e questo processo continua. Questo è noto come ereditarietà multilivello. Capiamo con un esempio:

class Parent: def f1(self): print("Function of parent class.")class Child_1(Parent): def f2(self): print("Function of child_1 class.")class Child_2(Child_1): def f3(self): print("Function of child_2 class.")obj_1 = Child_1()obj_2 = Child_2()obj_1.f1()obj_1.f2()print("\n")obj_2.f1()obj_2.f2()obj_2.f3()

Qui, la classe Child_1 eredita la classe Parent e la classe Child_2 eredita la classe Child_1. In questo Child_1 ha accesso alle funzioni f1() e f2() mentre Child_2 ha accesso alle funzioni f1(), f2() e f3(). Se proviamo ad accedere alla funzione f3() usando l’oggetto della classe Class_1 allora si verificherà un errore che dice:

L’oggetto ‘Child_1’ non ha l’attributo ‘f3’

obj_1.f3()

4- Ereditarietà gerarchica

In questo, varie classi Child ereditano una singola classe Parent. L’esempio dato nell’introduzione dell’eredità è un esempio di eredità gerarchica poiché le classi BMW e Audi ereditano la classe Car.

Per semplicità guardiamo un altro esempio:

class Parent:deff1(self):print("Function of parent class.")class Child_1(Parent):deff2(self):print("Function of child_1 class.")class Child_2(Parent):deff3(self):print("Function of child_2 class.")obj_1 = Child_1()obj_2 = Child_2()obj_1.f1()obj_1.f2()print('\n')obj_2.f1()obj_2.f3()

Qui due classi figlio stanno ereditando la stessa classe Genitore. La classe Bambino_1 ha accesso alle funzioni f1() della classe Genitore e alla funzione f2() di se stessa. Mentre la classe Bambino_2 ha accesso alle funzioni f1() della classe Genitore e alla funzione f3() di se stessa.

5- Ereditarietà ibrida

Quando c’è una combinazione di più di una forma di eredità, è conosciuta come ereditarietà ibrida. Sarà più chiaro dopo questo esempio:

class Parent: def f1(self): print("Function of parent class.")class Child_1(Parent): def f2(self): print("Function of child_1 class.")class Child_2(Parent): def f3(self): print("Function of child_2 class.")class Child_3(Child_1, Child_2): def f4(self): print("Function of child_3 class.")obj = Child_3()obj.f1()obj.f2()obj.f3()obj.f4()

In questo esempio, due classi ‘Child_1′ e ‘Child_2’ sono derivate dalla classe base ‘Parent’ usando l’ereditarietà gerarchica. Un’altra classe ‘Child_3’ deriva dalle classi ‘Child_1’ e ‘Child_2’ usando l’ereditarietà multipla. La classe ‘Child_3’ è ora derivata usando l’ereditarietà ibrida.

Method Overriding

Il concetto di overriding è molto importante nell’ereditarietà. Dà la capacità speciale alle sottoclassi/figli di fornire un’implementazione specifica ad un metodo che è già presente nelle loro classi genitrici.

class Parent: def f1(self): print("Function of Parent class.")class Child(Parent): def f1(self): print("Function of Child class.")obj = Child()obj.f1()

Qui la funzione f1() della classe Child ha sovrascritto la funzione f1() della classe Parent. Ogni volta che l’oggetto della classe Bambino invocherà f1(), la funzione della classe Bambino verrà eseguita. Tuttavia, l’oggetto della classe Genitore può invocare la funzione f1() della classe Genitore.

obj_2 = Parent()obj_2.f1()

La funzione super()

La funzione super() in Python ritorna un oggetto proxy che fa riferimento alla classe Genitore usando la parola chiave super. Questa parola chiave super() è fondamentalmente utile per accedere ai metodi sovrascritti della classe genitore.

La documentazione ufficiale della funzione super() ne individua due usi principali:
  1. In una gerarchia di classi con ereditarietà singola, super aiuta a fare riferimento alle classi madri senza nominarle esplicitamente, rendendo così il codice più manutenibile.

    Per esempio-

    class Parent: def f1(self): print("Function of Parent class.")class Child(Parent): def f1(self): super().f1() print("Function of Child class.")obj = Child()obj.f1()

    Qui, con l’aiuto di super().f1(), il metodo f1() della classe super della classe Child i.

    Una cosa da notare qui è che la classe super() può accettare due parametri – il primo è il nome della sottoclasse e il secondo è un oggetto che è un’istanza di quella sottoclasse. Vediamo come-
    class Parent: def f1(self): print("Function of Parent class.")class Child(Parent): def f1(self): super( Child, self ).f1() print("Function of Child class.")obj = Child()obj.f1()

    Il primo parametro si riferisce alla sottoclasse Child, mentre il secondo parametro si riferisce all’oggetto di Child che, in questo caso, è self. Potete vedere che l’output dopo aver usato super() e super( Child, self) è lo stesso perché, in Python 3, super( Child, self) è equivalente a self().

    Ora vediamo un altro esempio usando la funzione __init__.
    class Parent(object): def__init__(self, ParentName): print(ParentName, 'is derived from another class.')class Child(Parent): def__init__(self, ChildName): print(name,'is a sub-class.') super().__init__(ChildName)obj = Child('Child')

    Quello che abbiamo fatto qui è che abbiamo chiamato la funzione __init__ della classe Parent (dentro la classe Child) usando super().__init__( ChildName ). E poiché il metodo __init__ della classe Parent richiede un argomento, è stato passato come “ChildName”. Così, dopo aver creato l’oggetto della classe Child, prima viene eseguita la funzione __init__ della classe Child, e poi la funzione __init__ della classe Parent.

  2. Il secondo caso d’uso è quello di supportare le eredità multiple cooperative in un ambiente di esecuzione dinamica.
    class First(): def __init__(self): print("first") super().__init__()class Second(): def __init__(self): print("second") super().__init__()class Third(Second, First): def __init__(self): print("third") super().__init__()obj = Third()
    La chiamata super() trova il metodo successivo nella MRO ad ogni passo, motivo per cui anche First e Second devono averlo, altrimenti l’esecuzione si ferma alla fine di first().__init__.

    Nota che la superclasse di First e Second è Object.

    Cerchiamo anche la MRO di Third()

    Third.__mro__

    L’ordine è Terzo > Secondo > Primo e lo stesso è l’ordine del nostro output.

    Note finali

    Per concludere, in questo articolo ho portato avanti il concetto di Ereditarietà nella programmazione orientata agli oggetti in Python. Ho coperto varie forme di Ereditarietà e alcuni dei concetti comuni nell’Ereditarietà come l’Overriding dei metodi e la funzione super().

    Spero che abbiate capito i concetti spiegati in questo articolo. Fammi sapere nei commenti qui sotto se hai qualche domanda.

    Puoi leggere questo articolo anche sulla nostra APP mobile

Categorie: Articles

0 commenti

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *