Corso Python / Comprendere classi, istanze e self

Una classe definisce dati e comportamenti collegati; un’istanza è un singolo oggetto creato da quella definizione. Le classi sono utili quando uno stato e le regole che lo riguardano appartengono insieme: non serve trasformare ogni semplice calcolo in un oggetto.

Collegare una definizione alle sue istanze

La classe Expense può descrivere un tipo di spesa, mentre una spesa per il pranzo e una per il bus sono oggetti distinti. Condividono le definizioni dei metodi, ma ognuno mantiene il proprio nome e il proprio importo negli attributi d’istanza.

self è il nome convenzionale del primo parametro di un metodo d’istanza. Quando chiami first.describe(), Python passa automaticamente first a quel parametro. Nella forma Expense.describe(first) il destinatario viene invece indicato esplicitamente: per questo esempio le due chiamate producono lo stesso risultato.

__init__ prepara lo stato iniziale di un oggetto dopo che è stato creato. Chiamando Expense("lunch", 8000) fornisci name e amount, non self. L’assegnazione self.name memorizza il valore nell’istanza; la variabile locale name e l’attributo self.name hanno ruoli diversi.

La guida ufficiale alle classi Python spiega creazione delle istanze, attributi e collegamento dei metodi al loro destinatario.

Creare due spese con un esempio completo

Salva il codice completo come classes_demo.py, in UTF-8, in una cartella di esercizio. Nel terminale con l’interprete del progetto selezionato esegui python classes_demo.py. Fuori dall’ambiente virtuale su macOS o Ubuntu usa python3 quando è il comando dell’interprete scelto. Non servono file di dati o librerie esterne.

class Expense:
    def __init__(self, name, amount):
        if amount < 0:
            raise ValueError("negative amount")
        self.name = name
        self.amount = amount

    def describe(self):
        return f"{self.name}: {self.amount:,} KRW"

first = Expense("lunch", 8000)
second = Expense("bus", 2500)
print(first.describe())
print(second.describe())
print(first.amount + second.amount)

Il risultato previsto è:

lunch: 8,000 KRW
bus: 2,500 KRW
10500

Il metodo describe restituisce una stringa e print la mostra. Il formato {self.amount:,} aggiunge le virgole di raggruppamento alla rappresentazione dell’importo: non cambia il numero memorizzato. KRW è un’etichetta di valuta fissa nell’esempio, non un’operazione di conversione né una scelta automatica del formato locale italiano.

Il totale 10500 viene calcolato leggendo gli attributi dei due oggetti. L’addizione rimane una normale operazione tra numeri; aver introdotto una classe non richiede di spostare ogni operazione possibile all’interno di essa.

Mantenere responsabilità e regole esplicite

Il controllo iniziale rifiuta solo importi confrontabili con zero che risultino negativi. Non verifica in modo completo il tipo: una stringa può provocare TypeError nel confronto, mentre bool e valori frazionari possono superare il controllo. Se il contratto richiede soltanto interi non negativi, serve una regola ulteriore; non presentare questa inizializzazione come validazione di qualsiasi input.

La verifica durante __init__ non controlla ogni modifica successiva. Dopo la creazione puoi assegnare direttamente un importo negativo a first.amount e aggirare il controllo iniziale. Una classe ordinaria con attributi pubblici non è automaticamente un contenitore di dati immutabili o sempre validi.

La relazione iniziale nell’esempio è la seguente:

Nome Oggetto distinto Attributi iniziali
first Istanza A di Expense name = "lunch", amount = 8000
second Istanza B di Expense name = "bus", amount = 2500

Modificare l’importo dell’istanza A non cambia quello dell’istanza B. Al contrario, un’assegnazione come third = first non crea una terza spesa indipendente: collega un altro nome allo stesso oggetto. La differenza tra oggetti distinti e due nomi per lo stesso oggetto è essenziale per interpretare le modifiche.

Expense rappresenta qui un record e la sua descrizione. Aggiungere nella stessa classe stampa, file, database e richieste di rete senza un criterio rende più difficile provarla e riutilizzarla. Mantieni separate le operazioni esterne quando non fanno parte della responsabilità del record.

Verificare indipendenza, chiamate e limiti

  1. Dopo le stampe dell’originale aggiungi, in una copia di prova, l’assegnazione first.amount = 9000 e stampa entrambi gli importi. Devono risultare 9000 e 2500.
  2. Confronta Expense.describe(first) con first.describe(): entrambe le chiamate usano lo stesso oggetto come self.
  3. Crea un’istanza separata con importo -1. Il controllo iniziale deve sollevare ValueError con il messaggio negative amount; la gestione delle eccezioni permette di decidere dove segnalare o recuperare questo errore.
  4. Ripristina il codice originale e ricontrolla le tre righe dell’output previsto.

Un errore di battitura in assegnazione, per esempio first.ammount, può creare un nuovo attributo invece di aggiornare first.amount. Per strutture più grandi, test, annotazioni di tipo e dataclass possono rendere l’intento più chiaro, ma non risolvono tutti lo stesso problema e non garantiscono da soli il controllo automatico dei valori durante l’esecuzione.