L'assegnazione di una variabile (ad esempio, a = b) in Python non copia l'oggetto stesso, ma crea solo un nuovo nome (riferimento) per l'oggetto esistente. Le modifiche su un nome saranno visibili anche sull'altro, se si tratta di un oggetto mutabile.
Shallow copy (copia superficiale), ad esempio tramite copy.copy(obj) o slicing [:] per una lista, crea un nuovo oggetto di primo livello, ma gli oggetti annidati all'interno sono copiati per riferimento (cioè entrambe le strutture "guardano" gli stessi sottocontainers). Se si modificano gli oggetti annidati, le modifiche saranno visibili tramite entrambi gli oggetti.
Esempio:
import copy lst1 = [[1,2], [3,4]] lst2 = copy.copy(lst1) # oppure lst1[:] lst1[0][0] = 100 print(lst2) # [[100, 2], [3, 4]]
lst2 è una nuova lista, ma il suo primo elemento è la stessa lista annidata.
Domanda: Qual è la differenza tra lst2 = lst1[:] e lst2 = copy.copy(lst1)?
Risposta: In pratica, per liste normali (monolivello) non ci sono differenze — entrambi i metodi eseguono una copia superficiale della lista. Tuttavia, per classi contenitore personalizzate potrebbe esserci un comportamento diverso (ad esempio, se è implementato un proprio metodo __copy__). Allo stesso modo, per altri tipi (dict, set, ecc.) è più sicuro utilizzare il modulo specializzato copy.
import copy lst1 = [1, 2, 3] lst2 = lst1[:] lst3 = copy.copy(lst1) print(lst2 == lst3) # True
Storia
In un progetto di gestione delle configurazioni, uno sviluppatore ha duplicato i parametri predefiniti tramite assegnazione params = default_params e si aspettava di modificarli "in isolamento". Alla fine, qualsiasi modifica in una copia portava a un effetto a cascata in tutte le parti dell'applicazione, poiché in realtà si stava lavorando con un unico oggetto.
Storia
Un programmatore inesperto ha utilizzato una copia superficiale di una lista per tenere traccia dello stato del gioco (game_states = states[:]). Con una struttura annidata (liste delle figure sul campo), le modifiche all'interno di uno stato "filtravano" negli altri, rompendo la storia dei rollback e delle ripetizioni delle mosse.
Storia
Nel tentativo di clonare dati in un'applicazione OOP, la scelta era tra slicing e copy.copy(). Ma nella struttura si trovava una propria classe con un proprio metodo di copia, che è stato considerato solo quando si utilizzava copy.copy(). Lo slicing ha ignorato la logica di copia dell'oggetto, causando bug poco evidenti.