2. Variabili di Classe e Variabili di Istanza

In questa lezione spiegheremo la differenza tra variabili di istanza e variabili di classe, tramite degli esempi che vi permetteranno di capire cosa sono queste variabili e quando sia conveniente usare un tipo rispetto all'altro. Questo vi permetterà di ottenere una visuale più approfondita e coerente del concetto di classe, che nel video precedente abbiamo definito come la "fabbrica di oggetti".

Variabili di istanza

Nella lezione precedente abbiamo creato una semplice classe Studente. Ciascuna istanza di questa classe, quindi ciascuno studente creato a partire da questo modello generico, dispone di attributi che sono propri di sé stesso, come il suo nome, il suo cognome e il suo corso_di_studi. Questi vengono chiamati variabili di istanza, come ricorderete vengono impostati tramite self, che rappresenta proprio una referenza a ciascuna istanza.

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}"

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

Variabili di classe

Le variabili di classe invece sono attributi che vengono condivisi da tutte le istanze della classe.

Ad esempio, una caratteristica comune a tutti gli studenti di un'Istituto potrebbe essere il numero di ore di lezione settimanali. Potrebbe essere quindi una buona idea quella di definire una variabile di classe chiamata ore_settimanali, in modo che tutte le istanze abbiano questa proprietà senza bisogno di specificarla ogni volta.

Definire una variabile di classe è molto semplice:

class Studente:
    ore_settimanali = 36 # Variabile Di Classe
	
    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}"

Ora, volendo visualizzare il numero di ore settimanali nella scheda_personale, possiamo accedervi in due modi, tramite la classe o tramite l'istanza.


Come accedere alle variabili di classe tramite la classe

class Studente:
    ore_settimanali = 36
    
    # ...
	
    def scheda_personale(self):
        return f"Scheda Studente\n Nome:{self.nome}\n Cognome:{self.cognome}\n Corso Di Studi:{self.corso_di_studi}\n Ore Settimanali:{Studente.ore_settimanali}" #  <---QUI

Come accedere alla variabile di classe tramite l'istanza

class Studente:
    ore_settimanali = 36

    # ...

    def scheda_personale(self):
        return f"Scheda Studente\n Nome:{self.nome}\n Cognome:{self.cognome}\n Corso Di Studi:{self.corso_di_studi}\n Ore Settimanali:{self.ore_settimanali}"  #  <---QUI

Forse vi starete chiedendo: ma se si tratta di variabili di classe, come mai è possibile accedervi anche tramite l'istanza? È semplice: succede che, quando proviamo ad accedere all'attributo di un'oggetto, Python controlla anzitutto se l'istanza contiene quell'attributo, e se non è presente allora va a cercarlo come variabile di Classe. Facile no? Ciascuno dei metodi di accesso è preferibile a seconda del caso specifico che state modellando: nel nostro esempio delle ore settimanali potrebbe essere preferibile utilizzare un accesso tramite self, spiegheremo ora il perché.

Supponiamo che studente_uno decida di frequentare anche un corso serale di 4 ore aggiuntive settimanali, e che il sistema debba quindi incrementare il numero delle ore settimanali della sua specifica scheda personale. Accedere alla varibile di classe tramite l'istanza si dimostra in questo caso una mossa vincente, possiamo infatti fare:

studente_uno.ore_settimanali += 4

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

Scheda Studente
  Nome:Py
  Cognome:Mike
  Corso Di Studi:programmazione
  Ore Settimanali:40
 
Scheda Studente
  Nome:Marta
  Cognome:Stannis
  Corso Di Studi:scienze politiche
  Ore Settimanali:36

Così facendo, la nostra istanza si crea la sua versione personalizzata della variabile, senza influenzare gli oggetti restanti. Lasciate che ve lo dimostri visivamente:

print(Studente.__dict__)
print(studente_uno.__dict__)

{'__module__': '__main__', 'ore_settimanali': 36, '__init__': , 'scheda_personale': , '__dict__': , '__weakref__': , '__doc__': None}
{'nome': 'Py', 'cognome': 'Mike', 'corso_di_studi': 'programmazione'}

Come potete vedere, nella classe Studente è presente la variabile di classe ore_settimanali corrispondente a 36, mentre non è presente nell'istanza studente_uno.

Proviamo ora a ripetere il procedimento precedente, aggiungendo nuovamente 4 ore:

studente_uno.ore_settimanali += 4
{'__module__': '__main__', 'ore_settimanali': 36, '__init__': , 'scheda_personale': , '__dict__': , '__weakref__': , '__doc__': None}
{'nome': 'Py', 'cognome': 'Mike', 'corso_di_studi': 'programmazione', 'ore_settimanali': 40}

Ecco che ora abbiamo una variabile ore_settimanali anche in studente_uno, a cui è associato stavolta un 40.


Un altro esempio di variabile di classe

Facciamo ora un altro esempio di variabile di classe: vogliamo una variabile che rappresenti il numero totale di studenti dell'istituto. La mossa più saggia da fare in questo caso è quello di accedere alla variabile tramite la classe stessa, e faremo questo dal metodo __init__, in modo da incrementare il valore della variabile di classe ogni volta che uno studente viene inizializzato.

class Studente:
    ore_settimanali = 36
    corpo_studentesco = 0

    def __init__(self, nome, cognome, corso_di_studi):
        self.nome = nome
        self.cognome = cognome
        self.corso_di_studi = corso_di_studi
        Studente.corpo_studentesco += 1
	
    def scheda_personale(self):
        return f"Scheda Studente\n Nome:{self.nome}\n Cognome:{self.cognome}\n Corso Di Studi:{self.corso_di_studi}\n Ore Settimanali:{self.ore_settimanali}"
studente_uno = Studente("Py", "Mike", "programmazione")
studente_due = Studente("Marta", "Stannis", "scienze politiche")

print(Studente.corpo_studentesco)
2

Ci viene restituito 2 perché la variabile di casse corpo_studentesco è stata incrementata due volte durante la creazione dei due oggetti studente_uno e studente_due.