Corso Python / Distinguere attributi d’istanza e attributi di classe
Gli attributi d’istanza conservano lo stato di un singolo oggetto; gli attributi di classe possono offrire valori condivisi tramite la classe. Quando una lista deve essere indipendente per ogni oggetto, creala durante l’inizializzazione dell’istanza, invece di dichiararla una sola volta nel corpo della classe.
Individuare dove viene memorizzato l’attributo
Nel caso delle classi ordinarie di questa lezione, un’assegnazione come self.items = [] memorizza una nuova lista nell’istanza. La scrittura items = [] nel corpo della classe crea invece una lista appartenente alla classe; le istanze prive di un proprio items possono trovare e usare quella stessa lista.
La differenza non è soltanto sintattica. Chiamare append modifica l’oggetto lista già trovato: non crea automaticamente una lista privata. Se due istanze trovano la lista condivisa, una modifica fatta attraverso la prima diventa visibile anche attraverso la seconda.
Un’etichetta immutabile come currency può essere un attributo di classe. Assegnare b.currency crea, nell’esempio, un attributo di b che nasconde quello omonimo della classe senza modificarlo. Consulta la guida ufficiale agli attributi di classe e d’istanza per il confronto con le liste mutabili.
Queste regole proseguono il modello di classi, istanze e self: conta quale oggetto riceve l’operazione e quale valore sta effettivamente usando.
Creare registri indipendenti e una valuta propria
Salva il codice completo come attributes.py, in UTF-8, in una cartella di esercizio. Esegui python attributes.py nel terminale con l’interprete del progetto selezionato. Fuori dall’ambiente virtuale su macOS o Ubuntu usa python3 se è il comando dell’interprete scelto. Non servono librerie esterne.
class Ledger:
currency = "KRW"
def __init__(self, owner):
self.owner = owner
self.items = []
def add(self, amount):
self.items.append(amount)
a = Ledger("A")
b = Ledger("B")
a.add(3500)
print(a.items, b.items)
b.currency = "USD"
print(a.currency, b.currency, Ledger.currency)
Il risultato previsto è:
[3500] []
KRW USD KRW
A e B ricevono liste diverse perché __init__ esegue una nuova espressione [] per ogni istanza. Aggiungere 3500 ad a non inserisce nulla in b. Currency, invece, viene inizialmente trovata nella classe; dopo l’assegnazione a b, la lettura b.currency usa il suo valore USD, mentre a.currency e Ledger.currency restano KRW.
Queste stringhe sono etichette: cambiare currency non converte gli importi. Inoltre add si limita ad aggiungere il valore ricevuto e non ne controlla tipo o segno; un registro operativo deve definire anche queste regole, separatamente dalla scelta di memorizzazione.
Confrontare una lista condivisa con liste separate
Salva il secondo esempio completo in un altro file, shared_lists.py, mantenendo attributes.py per il confronto. Esegui python shared_lists.py dalla stessa cartella di esercizio:
class Shared:
items = []
class Separate:
def __init__(self):
self.items = []
a, b = Shared(), Shared()
a.items.append(1)
print(a.items, b.items)
x, y = Separate(), Separate()
x.items.append(1)
print(x.items, y.items)
Il risultato previsto è:
[1] [1]
[1] []
Nel primo caso a.items e b.items raggiungono la stessa lista Shared.items. Nel secondo, x.items e y.items sono stati creati separatamente. Il fatto che la definizione del metodo sia comune non rende comuni le liste create durante ciascuna chiamata a __init__.
Anche l’inizializzazione con un elenco fornito dal chiamante richiede una scelta: assegnarlo direttamente conserva lo stesso oggetto. Una copia superficiale separa la lista esterna, ma può continuare a condividere gli elementi mutabili annidati. Stabilisci se il contratto vuole condivisione o indipendenza prima di decidere come copiare.
Se cambi successivamente Ledger.currency, le istanze che non hanno un attributo proprio possono vedere il nuovo valore; b continua invece a usare USD. Eliminare l’attributo b.currency fa tornare visibile quello della classe. Se queste eccezioni rendono difficile il significato della configurazione, ricevere la valuta come argomento di inizializzazione può essere più chiaro.
Riprodurre e correggere una condivisione involontaria
- In una copia di attributes.py aggiungi items = [] nel corpo della classe Ledger e rimuovi self.items = [] da __init__. Mantieni il resto uguale.
- Esegui la copia: dopo a.add(3500), sia a.items sia b.items devono mostrare [3500]. La lista è stata modificata attraverso un’istanza, ma si trova nella classe.
- Ripristina self.items = [] dentro __init__ e rimuovi la lista di classe. Avvia un nuovo processo e verifica di nuovo [3500] [] seguito da KRW USD KRW.
Per ogni confronto riparti dal sorgente indicato e da un nuovo processo, così modifiche lasciate nella sessione non alterano l’osservazione. Una raccolta condivisa può essere intenzionale, ma allora durata, accessi concorrenti e responsabilità devono essere progettati esplicitamente; non usare per caso una lista di classe come deposito globale.




