1. Classi e Istanze

In questa nuova serie tratteremo alcune tematiche della programmazione a oggetti (in inglese OOP: Object-Oriented Programming) nel mondo di Python, partendo oggi da classi e istanze: in questa lezione impareremo come crearle e utilizzarle e qual è la differenza tra le due. Iniziamo rispondendo alla domanda più attesa: cosa sono le classi e a cosa ci servono?


Classi e istanze: che cosa sono?

Le classi sono un fantastico strumento che ci permette di raggruppare variabili e funzioni nei nostri programmi in maniera logica e riutilizzabile: questo ci permette di gestire progetti anche di grosse dimensioni in maniera molto semplice.

Supponiamo di dover scrivere un'applicazione che gestisca l'aggiunta di studenti al database di una scuola; ogni studente avrà attributi come nome, cognome e corso_di_studi seguito, più alcune funzioni associate che chiameremo metodi.

Ora, le scelte sono due: nella nostra applicazione possiamo creare ciascuno studente singolarmente con tutte le sue proprietà e funzioni, il che chiederebbe tantissimo tempo, oppure trovare un modo per creare le varie entità studente in maniera rapida, come quando si fanno i biscotti e si usano gli stampini.

In contesti come questo, usare le classi risulta comodissimo perché ci permettono di modellare la realtà in maniera efficiente e come abbiamo detto riutilizzabile; grazie alle classi possiamo definire le proprietà di ciascuno studente in un modello generico chiamato classe e da qui far derivare ciascun singolo individuo assegnandoli i suoi attributi personali, vedremo a brevissimo come.

Una classe viene spesso metaforizzata come una "fabbrica di oggetti", e ciascun oggetto creato a partire da questo modello generico viene chiamato istanza. Quindi una classe non è altro che un sistema usato per modellare la realtà in modo da poter costruire e gestire oggetti più o meno complessi. Nel nostro esempio avremo quindi un modello generico di studente chiamato classe Studente, ed ogni studente creato sarà un'istanza di questo modello.


Il metodo __init__()

In Python, per creare una classe Studente ci basta fare:

class Studente:

Ora, possiamo iniziare ad aggiungere al nostro modello generico i vari attributi di uno Studente, abbiamo detto nome, nognome e corso di studi. Per aggiungere queste caratteristiche usiamo una funzione speciale, chiamata metodo __init__, che significa inizializzatore, conosciuto anche come metodo costruttore. Il suo scopo nella "fabbrica" è proprio quello di costruire gli oggetti: diciamo che fa buona parte del lavoro sporco per noi!

class Studente:
    def __init__(self):

Quando creiamo dei metodi all'interno della classe, tra le parentesi di questi metodi passiamo, come primo parametro, l'istanza della classe, e la chiamiamo per convenzione self, che in Italiano significa sé stesso. Spiegheremo a brevissimo il perché di tutto questo, aggiungiamo prima il resto degli attributi:

class Studente:
    def __init__(self, nome, cognome, corso_di_studi):
        self.nome = nome
        self.cognome = cognome
        self.corso_di_studi = corso_di_studi


Il parametro self

Ora, abbiamo detto che ai metodi viene passato self, quindi l'istanza, perché rappresenta l'oggetto a cui dovranno essere associate il resto delle proprietà passate, nome, cognome ecc. Abbiamo detto che la classe è un modello generico, e quindi deve essere valido per ogni studente creato, giusto? self rappresenta quindi una referenza a ciascun oggetto creato dalla classe, e il metodo __init__ inizializza e attiva le varie proprietà di ciascun self, quindi di ciascun oggetto o istanza.

Quindi per ciascuno dei self, ovvero degli studenti inizializzati, il nome sarà corrispondente al nome passato ad __init__, il cognome di ciascun self sarà corrispondente al cognome passato, e stesso discorso per il corso di studi. Questi attributi prendono il nome di variabili dell'istanza.

Ci Siamo? Facciamo così, inizializziamo due istanze passando alla nostra fabbrica due set di dati così che ci vada a costruire due entità studente.

studente_uno = Studente("Py", "Mike", "programmazione")
studente_due = Studente("Marta", "Stannis", "scienze politiche")

Proviamo a passare a print() sia studente_uno che studente_due e vediamo cosa ci restituisce:

print(studente_uno)
print(studente_due)

<__main__.Studente object at 0x7f4fdfcd26d8>
<__main__.Studente object at 0x7f4fdfcd28d0>

Come vedete dall'output di print(), si tratta di oggetti di tipo studente, quindi creati dal nostro modello generico della classe Studente, presenti in due allocazioni di memoria diverse, ciascuno con le sue proprietà, ciascuno con la sua zona di memoria. Volendo, ora che il modello è stato progettato, potete creare centinaia di studenti a partire dalla stessa classe, in maniera estremamente semplice e conveniente!


Come chiamare i metodi di classe sull'istanza

Ora, portiamo il tutto un gradino più in alto, aggiungendo un secondo metodo alla nostra classe, una funzione che ci permetta di visualizzare la scheda personale di ciascuno studente.

class Studente:
    def __init__(self, nome, cognome, corso_di_studi):
        self.nome = nome
        self.cognome = cognome
        self.corso_di_studi = corso_di_studi

    def scheda_personale(self):
        return f"Scheda Studente\n Nome:{self.nome}\n Cognome:{self.cognome}\n Corso Di Studi:{self.corso_di_studi}"

Come abbiamo detto in precedenza, ad ogni metodo della classe viene passato anzitutto self, quindi l'istanza: ogni attributo passato alla scheda va preceduto da self, quindi self.nome, self.cognome ecc, per fare in modo che il metodo scheda_personale funzioni su ciascuna istanza della classe.

Ora possiamo richiamare questo metodo su qualsiasi oggetto abbiamo creato:

print(studente_uno.scheda_personale())
print(studente_due.scheda_personale())

Scheda Studente
  Nome: Py
  Cognome: Mike
  Corso Di Studi: programmazione

Scheda Studente
  Nome: Marta
  Cognome: Stannis
  Corso Di Studi: scienze politiche


Come chiamare i metodi sulla classe stessa

Prima di chiudere voglio farvi notare un'ultimo dettaglio che vi permetterà di chiarire ulteriormente il nesso tra classe, istanza e self. Oltre a chiamare i metodi della classe sull'istanza, come abbiamo appena fatto, possiamo chiamarli anche sulla classe stessa:

print(Studente.scheda_personale(studente_uno))

Scheda Studente
  Nome: Py
  Cognome: Mike
  Corso Di Studi: programmazione

Come vedete i risultati sono identici a quelli ottenuti precedentemente, con la differenza che stavolta come parametro abbiamo dovuto passare il nome dell'istanza per cui volevamo ottenere la scheda personale.

Di fatto, questo è ciò che succede in maniera automatica quando chiamiamo il metodo su ciascun oggetto, ed è proprio per questo motivo che utilizziamo self, che rappresenta quindi l'istanza passata automaticamente!