Comprendere come Python passa gli argomenti alle funzioni è estremamente importante per prevenire cambiamenti imprevisti nei dati e per una corretta progettazione del codice.
Nei linguaggi di programmazione tradizionali, come C o Java, si utilizza il passaggio per valore (copy by value) o per riferimento (copy by reference). Tuttavia, in Python esiste un modello diverso — call by object reference (a volte chiamato "call by sharing").
Molti sviluppatori pensano erroneamente che Python passi sempre gli argomenti o per riferimento o per valore. Questo porta inevitabilmente a situazioni in cui gli oggetti mutabili vengono inaspettatamente modificati nel codice chiamante.
In Python i valori dei parametri della funzione sono riferimenti agli oggetti che vengono passati alla funzione. Questo significa:
Esempio:
# list - mutabile (mutable) def add_item(lst): lst.append(42) my_list = [1, 2, 3] add_item(my_list) print(my_list) # [1, 2, 3, 42] # int - immutabile (immutable) def add_num(x): x = x + 1 num = 10 add_num(num) print(num) # 10
Caratteristiche chiave:
Vengono sempre passati per riferimento gli argomenti in Python?
No, in Python vengono passati riferimenti agli oggetti, e come si comporta un oggetto dipende da se è mutabile o meno. Un oggetto immutabile, in caso di modifica, creerà sempre un nuovo oggetto.
È possibile riassegnare un argomento mutabile in una funzione e far sì che questo influisca sull'oggetto esterno?
No. Se dentro la funzione assegni un nuovo valore a un parametro, l'oggetto esterno non cambierà — stai solo cambiando un riferimento locale.
Esempio:
def reassign_list(lst): lst = [99, 100] my_list = [1, 2, 3] reassign_list(my_list) print(my_list) # [1, 2, 3]
Perché una funzione che accetta un list per default può comportarsi in modo strano a chiamate successive?
Perché il valore predefinito viene creato una sola volta — al momento della definizione della funzione, e se viene modificato (ad esempio, aggiungendo un elemento), verrà modificato per tutte le chiamate successive.
def add_element(x, cache=[]): cache.append(x) return cache print(add_element(1)) # [1] print(add_element(2)) # [1, 2]
Un programmatore passa un elenco a una funzione e si aspetta che il suo elenco originale non cambi, ma la funzione aggiunge un elemento.
Pro:
Contro:
Un programmatore copia esplicitamente l'elenco all'interno della funzione, se deve restituire qualcosa ma non modificare l'originale:
def process_data(data): data = data.copy() # o list(data) # lavoro sicuro con la copia data.append('report') return data
Pro:
Contro: