ProgrammazioneSviluppatore Python / Data Engineer

Cosa succede quando si passa un oggetto mutabile (ad esempio, una lista o un dizionario) a una funzione Python? Come evitare modifiche impreviste all'interno e all'esterno della funzione?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

Storia della domanda:

Il passaggio dei parametri in Python implementa il principio "call by object reference" (a volte chiamato call by sharing). Ciò significa che la variabile all'interno della funzione inizia a puntare allo stesso oggetto in memoria dell'argomento passato dall'esterno.

Problema:

Se la funzione modifica l'oggetto mutabile passato (ad esempio, una lista o un dizionario), le modifiche sono visibili anche all'esterno della funzione. Questo può portare a bug difficili da rilevare, specialmente se si presume che la funzione non modifichi i dati in ingresso.

Soluzione:

Per evitare effetti collaterali, è necessario creare una copia dell'oggetto all'interno della funzione oppure utilizzare strutture dati immutabili. Per la copia si usano metodi standard (ad esempio, list.copy() per le liste, dict.copy() per i dizionari o copy.deepcopy()).

Esempio di codice:

def append_one(xs): xs.append(1) return xs lst = [0] append_one(lst) print(lst) # [0, 1] # Come evitare modifiche? Creare una copia: def safe_append_one(xs): ys = xs.copy() ys.append(1) return ys lst2 = [0] safe_append_one(lst2) print(lst2) # [0]

Caratteristiche chiave:

  • Il passaggio di un oggetto mutabile consente di cambiarne lo stato sia all'interno che all'esterno della funzione.
  • Per evitarlo si utilizza la copia dei dati (copie superficiali/profonde).
  • Gli oggetti immutabili sono protetti da tali modifiche.

Domande trabocchetto.

Si può essere certi che una copia di una lista tramite .copy() sia completamente indipendente dalla lista originale?

No — .copy() crea una copia superficiale. Se ci sono oggetti mutabili annidati, le modifiche a questi saranno visibili anche nell'originale.

import copy lst = [[1, 2], [3, 4]] shallow = lst.copy() shallow[0][0] = 42 print(lst) # [[42, 2], [3, 4]] deep = copy.deepcopy(lst) deep[0][0] = 100 print(lst) # [[42, 2], [3, 4]]

Restituire un nuovo oggetto basato sull'input garantisce l'assenza di modifiche nell'originale?

Non sempre. Se il nuovo oggetto utilizza parti dell'originale (ad esempio, un riferimento a una lista interna), l'oggetto originale può essere modificato.

def duplicate_list(xs): return xs * 2 lst = [[1], [2]] res = duplicate_list(lst) res[0][0] = 999 print(lst) # [[999], [2]]

Possono i valori di default per gli argomenti mutabili causare problemi durante chiamate multiple della funzione?

Sì — il valore di default viene calcolato solo una volta durante la definizione della funzione.

def add_item(item, container=[]): container.append(item) return container print(add_item(1)) # [1] print(add_item(2)) # [1, 2]

Errori comuni e anti-pattern

  • Modifica di un oggetto mutabile passato nella funzione senza avvisare l'utente.
  • Uso della copia superficiale per strutture dati annidate (errore con oggetti mutabili annidati).
  • Uso di oggetti mutabili come valori di default negli argomenti della funzione.

Esempio di vita reale

Caso negativo

In una libreria di gestione delle configurazioni è stata utilizzata una lista come valore di default, che ha portato all'accumulo di elementi tra diverse chiamate della funzione. Il comportamento era imprevedibile e difficile da rilevare nel lungo periodo.

Pro:

Meno codice per chiamate ripetute, risparmio di memoria visibile.

Contro:

Comportamento implicito, difficoltà nella debug, errori a lungo termine.

Caso positivo

Uso di None come valore di default e creazione esplicita di un nuovo oggetto ad ogni chiamata.

Pro:

Prevedibilità, assenza di effetti collaterali imprevisti, affidabilità.

Contro:

Richiede consapevolezza e un po' più di codice.