ProgrammazioneSviluppatore Python (middleware, backend, test)

Come implementare correttamente la copia delle collezioni in Python, in quali casi copy() non è sufficiente e perché, e quando è necessaria una deep copy?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

Storia della questione: In Python, la copia di oggetti (soprattutto collezioni come liste e dizionari) è critica: una semplice assegnazione a una variabile crea un nuovo riferimento, non una copia. I metodi speciali copy() e deepcopy() sono stati introdotti per evitare effetti collaterali indesiderati nell'uso condiviso delle strutture.

Problema: Quando si lavora con collezioni annidate (lista di liste, dizionario dentro un dizionario), una semplice copy() riproduce solo il contenitore stesso, ma non gli elementi interni. Questo può portare a bug difficili da individuare: la modifica di un elemento annidato si riflette in tutte le "copie".

Soluzione: copy.copy() crea una copia superficiale (shallow copy) — un nuovo contenitore di livello superiore, ma gli oggetti annidati rimangono gli stessi. copy.deepcopy() copia ricorsivamente tutti gli oggetti annidati.

Esempio di codice:

import copy lst = [[1, 2], [3, 4]] shallow = copy.copy(lst) deep = copy.deepcopy(lst) lst[0][0] = 10 print(shallow) # [[10, 2], [3, 4]] — l'oggetto annidato è cambiato! print(deep) # [[1, 2], [3, 4]] — deepcopy ha mantenuto lo stato originale

Caratteristiche chiave:

  • Una semplice assegnazione copia solo il riferimento, nessuna duplicazione
  • .copy() (o copy.copy()) copia il contenitore "esterno", ma non gli oggetti annidati
  • .deepcopy() è usato per una completa indipendenza delle copie

Domande insidiose.

È sufficiente a volte scrivere lst2 = lst[:] per copiare una lista?

lst2 = lst[:] crea una copia superficiale della lista, ma gli oggetti annidati (ad esempio, liste all'interno della lista) saranno comunque gli stessi. Per liste piatte, è sufficiente; per strutture annidate, non lo è.

Esempio:

lst = [[1], [2]] lst2 = lst[:] lst[0][0] = 99 print(lst2) # [[99], [2]] — l'elemento annidato è cambiato in entrambe le "copie"

Il metodo .copy() funziona allo stesso modo per tutte le collezioni?

No. Ad esempio, dict.copy() funziona come copia superficiale, list.copy() è apparso solo con Python 3.3, per set c'è set.copy(). Per oggetti utente, il supporto di .copy() dipende dal metodo implementato.


Si può farne a meno di deepcopy ovunque? È sicuro ed efficiente?

No. deepcopy è un'operazione costosa: può causare problemi di prestazioni, specialmente su strutture grandi, e può rompersi su oggetti non copiabili/chiusi o non standard. Usa deepcopy solo quando è davvero necessario un copia ricorsiva completamente indipendente.


Errori comuni e anti-pattern

  • Usare una semplice assegnazione o .copy() invece di deepcopy per strutture annidate
  • Applicare deepcopy su tutti gli oggetti di seguito (rallenta il programma)
  • Cercare di copiare oggetti che non supportano la copia (ad esempio, file, stream)

Esempio dalla vita reale

Caso negativo

Nei test si è tentato di copiare un "dizionario di dizionari" tramite dict.copy(). A causa di ciò, le modifiche nella struttura annidata per un utente influenzavano improvvisamente altri test (i dati venivano mutati globalmente).

Pro:

  • Veloce
  • Facile

Contro:

  • Bug non evidenti, rottura dell'isolamento dell'ambiente

Caso positivo

È stato implementato copy.deepcopy(), descritto nella documentazione il livello di annidamento e le caratteristiche di ciascuna struttura d'oggetto, evitando deepcopy per grandi volumi, dove era possibile fare una copia "manuale" in parti.

Pro:

  • Trasparenza dell'ambiente
  • Indipendenza degli oggetti

Contro:

  • Richiede comprensione della struttura
  • Consumo di memoria e tempo di elaborazione molto maggiore per strutture grandi