lettura facile

Gli attributi di classe e attributi di istanza in Python

Quando crei una classe in Python, puoi definire due tipi di attributi: quelli di classe e quelli di istanza. Sono strumenti che ti aiutano a organizzare le informazioni in modi diversi a seconda delle tue necessità.

  • Attributi di classe
    Sono gli attributi che condividi con ogni istanza della tua classe. Pensali come regole o proprietà generali che valgono per tutti gli oggetti di quella classe, senza eccezioni. Se decidi di cambiare un attributo di classe, quel cambiamento si riflette su tutte le istanze. È come se cambiassi le regole del gioco per tutti i giocatori contemporaneamente. Sono definiti direttamente all'interno di una classe e al di fuori di qualsiasi metodo.
  • Attributi di istanza
    A differenza degli attributi di classe, quelli di istanza sono specifici per ogni oggetto che crei utilizzando la classe. Ciò significa che ogni oggetto può avere valori diversi per questi attributi. Sono i dettagli personali che rendono ogni istanza unica. Se hai due oggetti, cambiare un attributo di istanza in uno non influenzerà l'altro. Questi attributi sono definiti all'interno di un metodo, tipicamente il metodo __init__ che è il costruttore della classe.

Questa distinzione tra gli attributi ti permette di gestire facilmente sia le proprietà condivise che quelle individuali. Ad esempio, tutte le automobili hanno quattro ruote, è una caratteristica condivisa. Se avessero tre o cinque ruote non potremmo più definire questi veicoli come "automobili". Viceversa, il colore e la marca sono proprietà che possono cambiare in ogni automobile.

Facciamo un esempio concreto.

Supponiamo di avere una classe `Auto`. Ogni auto ha un numero fisso di ruote, ma la marca e il colore possono variare da un'auto all'altra.

class Auto:
     # Attributo di classe
     ruote = 4

     def __init__(self, marca, colore):
         # Attributi di istanza
         self.marca = marca # la marca può variare
         self.colore = colore # il colore può variare

# Crea due oggetti
auto1 = Auto("Tesla", "rosso")
auto2 = Auto("Toyota", "blu")

# Stampa dei dettagli delle auto
print(f"L'auto1 è una {auto1.marca} di colore {auto1.colore} e ha {auto1.ruote} ruote")
print(f"L'auto2 è una {auto2.marca} di colore {auto2.colore} e ha {auto2.ruote} ruote")

In questo caso 'ruote' è un attributo di classe che applichi uniformemente a tutte le auto, perché tutte le automobili hanno quattro ruote.

Gli attributi 'marca' e 'colore' sono, invece, degli attributi di istanza che differenziano una Tesla rossa da una Toyota blu.

Ecco come si presenta il risultato sullo schermo quando esegui questo script.

L'auto1 è una Tesla di colore rosso e ha 4 ruote
L'auto2 è una Toyota di colore blu e ha 4 ruote

Se decidi di cambiare il 'colore' dell'oggetto 'auto1', questa modifica non altera il contenuto dell'attributo 'colore' nell'altro oggetto 'auto2'.

class Auto:
     # Attributo di classe
     ruote = 4

     def __init__(self, marca, colore):
         # Attributi di istanza
         self.marca = marca # la marca può variare
         self.colore = colore # il colore può variare

# Crea due oggetti
auto1 = Auto("Tesla", "rosso")
auto2 = Auto("Toyota", "blu")

# Cambia il colore di auto1
auto1.colore = "bianco"

# Stampa dei dettagli delle auto
print(f"L'auto1 è una {auto1.marca} di colore {auto1.colore} e ha {auto1.ruote} ruote")
print(f"L'auto2 è una {auto2.marca} di colore {auto2.colore} e ha {auto2.ruote} ruote")

L'auto1 è una Tesla di colore bianco e ha 4 ruote
L'auto2 è una Toyota di colore blu e ha 4 ruote

Questo accade perché l'attributo 'colore' è un attributo di istanza.

Cosa accade se modifichi l'attributo di classe tramite un'istanza

Python ti permette di modificare anche gli attributi di classe direttamente tramite le istanze, ma quando assegni un valore a un attributo di classe attraverso un'istanza, in realtà stai creando un nuovo attributo di istanza per quell'oggetto specifico.

In altre parole, questo nuovo attributo "oscura" l'attributo di classe originale per quella particolare istanza.

Ad esempio, se modifichi l'attributo 'auto1.ruote=5', stai assegnando all'oggetto auto1 un nuovo attributo di istanza 'ruote' che non influisce su auto2.

Per auto2 l'attributo 'ruote' rimane ancora un riferimento all'attributo di classe ed è ancora 4. 

class Auto:
     # Attributo di classe
     ruote = 4

     def __init__(self, marca, colore):
         # Attributi di istanza
         self.marca = marca # la marca può variare
         self.colore = colore # il colore può variare

# Crea due oggetti
auto1 = Auto("Tesla", "rosso")
auto2 = Auto("Toyota", "blu")

# Cambia il colore di auto1
auto1.ruote = 5

# Stampa dei dettagli delle auto
print(f"L'auto1 è una {auto1.marca} di colore {auto1.colore} e ha {auto1.ruote} ruote")
print(f"L'auto2 è una {auto2.marca} di colore {auto2.colore} e ha {auto2.ruote} ruote")

L'auto1 è una Tesla di colore rosso e ha 5 ruote
L'auto2 è una Toyota di colore blu e ha 4 ruote

Questo comportamento è molto importante per capire come Python gestisce la distinzione tra attributi di classe e di istanza.

Se vuoi modificare l'attributo a tutte le istanze, devi modificare l'attributo di classe.

Ad esempio, aggiungi alla fine del codice 'auto.ruote.5' per modificare l'attributo di classe.

In questo caso la modifica si propaga automaticamente su tutti gli oggetti che hai creato tramite la classe.

class Auto:
     # Attributo di classe
     ruote = 4

     def __init__(self, marca, colore):
         # Attributi di istanza
         self.marca = marca # la marca può variare
         self.colore = colore # il colore può variare

# Crea due oggetti
auto1 = Auto("Tesla", "rosso")
auto2 = Auto("Toyota", "blu")

# Cambia il colore di auto1
auto.ruote = 5

# Stampa dei dettagli delle auto
print(f"L'auto1 è una {auto1.marca} di colore {auto1.colore} e ha {auto1.ruote} ruote")
print(f"L'auto2 è una {auto2.marca} di colore {auto2.colore} e ha {auto2.ruote} ruote")

L'auto1 è una Tesla di colore rosso e ha 5 ruote
L'auto2 è una Toyota di colore blu e ha 5 ruote

Namespace degli attributi di classe e di istanza

Gli attributi di una classe sono conservati nel dizionario __dict__ proprio della classe.

Viceversa, gli attributi specifici di un'istanza sono conservati nel dizionario __dict__ dell'istanza stessa.

Ad esempio, definisci questa classe e crea un'istanza.

class Lampada:
      materiale = "metallo" # Attributo di classe
      def __init__(self, colore):
          self.colore = colore # Attributo di istanza

# Creazione di due istanze della classe Lampada
lampada1 = Lampada("rosso")

Se accedi al dizionario della classe trovi l'attributo "materiale" che è condiviso con tutte le istanze create con questa classe.

print(Lampada.__dict__)

{'__module__': '__main__', 'materiale': 'metallo', '__init__': <function Lampada.__init__ at 0x7ff37a867910>, '__dict__': <attribute '__dict__' of 'Lampada' objects>, '__weakref__': <attribute '__weakref__' of 'Lampada' objects>, '__doc__': None}

Se invece interroghi il dizionario dell'istanza trovi l'attributo "colore".

print(lampada1.__dict__)

{'colore': 'rosso'}

Quando accedi a un attributo attraverso un'istanza, Python cerca inizialmente nel __dict__ dell'istanza.

Se l'attributo non viene trovato, la ricerca procede nel __dict__ della classe.

print(lampada1.materiale)

metallo

Fai attenzione se gli attributi di classe e di istanza hanno lo stesso nome.

Se esiste un'attribuzione con lo stesso nome sia nell'istanza sia nella classe, l'attributo dell'istanza avrà la precedenza, oscurando l'attributo di classe quando si accede all'attributo attraverso l'istanza.

L'uso di attributi di classe è utile ma è essenziale gestirli con attenzione per evitare confusione o comportamenti inaspettati nel tuo codice.




Se qualcosa non ti è chiaro, scrivi la tua domanda nei commenti.




FacebookTwitterLinkedinLinkedin