Skip to content
> 💻 🧠 Codice 1001 > 📑 Schede Riassuntive > 🐍 Schede Riassuntive Python > Guida alla copia degli oggetti in Python

Guida alla copia degli oggetti in Python

🔎 Perché la copia degli oggetti è così importante?

In Python, le variabili non sono “scatole” che contengono dati, ma piuttosto “etichette” o riferimenti che puntano a oggetti in memoria. Quando si esegue una semplice assegnazione, come questa:

lista_a = [1, 2, 3]
lista_b = lista_a

Non stai creando una nuova lista. Stai semplicemente creando un altro riferimento (lista_b) che punta esattamente allo stesso oggetto lista.

👉 Qualsiasi modifica a lista_a si rifletterà immediatamente su lista_b (e viceversa), perché sono lo stesso oggetto.

Analogia: Immagina di avere un documento Google Docs. Se invii un link a un amico, lui modificherà il tuo documento originale. Questa è un’assegnazione. Se invece selezioni “File” → “Crea una copia”, verrà creato un documento completamente indipendente. Il modulo copy in Python esiste proprio per creare questo tipo di copie indipendenti.

⚙️ Due tipi di copia

Il modulo copy fornisce due funzioni chiave:

  • copy.copy() — crea una copia superficiale (shallow copy).
  • copy.deepcopy() — crea una copia profonda (deep copy).

La differenza principale tra le due risiede nel modo in cui gestiscono gli oggetti annidati (come liste all’interno di liste o dizionari all’interno di dizionari).


🟦 Copia superficiale (copy.copy())

copy.copy() crea un nuovo oggetto contenitore di primo livello, ma invece di copiare gli oggetti annidati, inserisce al suo interno dei riferimenti agli oggetti originali.

import copy

lista_originale = [1, 2, [3, 4]]
copia_superficiale = copy.copy(lista_originale)

# Modifica la lista annidata nell'originale
lista_originale[2][0] = 99

print(f"Originale: {lista_originale}")    # Output: Originale: [1, 2, [99, 4]]
print(f"Copia superficiale: {copia_superficiale}") # Output: Copia superficiale: [1, 2, [99, 4]]

Come puoi vedere, la modifica della lista annidata ha interessato sia l’originale che la copia, perché condividono la stessa lista annidata.

Diagramma ASCII:

lista_originale    → [ 1, 2, ───► lista_interna ]
copia_superficiale → [ 1, 2, ───► lista_interna ]
                                lista_interna   → [ 99, 4 ]

🟩 Copia profonda (copy.deepcopy())

copy.deepcopy() risolve il problema dei riferimenti condivisi. Questa funzione attraversa ricorsivamente l’intero oggetto e crea copie complete e indipendenti di tutto ciò che incontra, inclusi tutti gli oggetti annidati.

import copy

lista_originale = [1, 2, [3, 4]]
copia_profonda = copy.deepcopy(lista_originale)

# Modifica la lista annidata nell'originale
lista_originale[2][0] = 99

print(f"Originale: {lista_originale}") # Output: Originale: [1, 2, [99, 4]]
print(f"Copia profonda: {copia_profonda}")     # Output: Copia profonda: [1, 2, [3, 4]]

Ora, copia_profonda è completamente indipendente.

Diagramma ASCII:

lista_originale → [ 1, 2, ───► lista_a ]
copia_profonda  → [ 1, 2, ───► lista_b ]
                             lista_a   → [ 99, 4 ]
                             lista_b   → [ 3, 4 ]

📑 Tabella riassuntiva: copy.copy() vs copy.deepcopy()

Tipo di oggettoEsempiocopy.copy() (superficiale)copy.deepcopy() (profonda)
list (lista)[1, 2, [3, 4]]Una nuova lista, ma la lista annidata [3, 4] è condivisaUna copia completamente indipendente; anche la lista annidata viene copiata
dict (dizionario){"a": 1, "b": {"c": 2}}Un nuovo dizionario, ma il {"c": 2} annidato è condivisoUn dizionario indipendente e tutti i dizionari annidati
set{1, 2, (3, 4)}Un nuovo set; gli elementi mutabili annidati sarebbero condivisiUn nuovo set; tutti gli elementi annidati vengono copiati ricorsivamente
tuple (tupla)(1, 2, [3, 4])Restituisce la stessa tupla, ma la lista annidata è condivisaUna nuova tupla con una copia della lista annidata
str, int, float, bool"ciao", 42, 3.14, TrueRestituisce lo stesso oggetto (sono immutabili, non serve copiarli)Come la copia superficiale
classe personalizzataNodo(1, Nodo(2))Un nuovo oggetto di primo livello; gli oggetti annidati sono condivisiUna copia completamente indipendente dell’oggetto e di tutti gli oggetti annidati
oggetto con __slots__Una classe con __slots__Viene copiato solo l’oggetto di primo livelloVengono copiati tutti gli oggetti annidati se supportano la copia
file / socket / streamopen("file.txt")Errore (oggetto non copiabile)Errore (oggetto non copiabile)

🧪 Esempi pratici per la tabella

📌 list

import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f"copy: {b}")      # [1, 2, [99, 4]]
print(f"deepcopy: {c}")  # [1, 2, [3, 4]]

📌 dict

import copy
a = {"a": 1, "b": {"c": 2}}
b = copy.copy(a)
c = copy.deepcopy(a)
a["b"]["c"] = 42
print(f"copy: {b}")      # {'a': 1, 'b': {'c': 42}}
print(f"deepcopy: {c}")  # {'a': 1, 'b': {'c': 2}}

📌 set

import copy
a = {1, 2, (3, 4)} # I set possono contenere solo tipi immutabili
b = copy.copy(a)
c = copy.deepcopy(a)
print(b == c) # True (ma sono due oggetti indipendenti in memoria)

📌 tuple

import copy
a = (1, 2, [3, 4]) # La tupla è immutabile, ma il suo elemento lista no
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f"copy: {b}")      # (1, 2, [99, 4])
print(f"deepcopy: {c}")  # (1, 2, [3, 4])

📌 Classe personalizzata

import copy
class Nodo:
    def __init__(self, valore, figlio=None):
        self.valore = valore
        self.figlio = figlio

a = Nodo(1, Nodo(2))
b = copy.copy(a)
c = copy.deepcopy(a)
a.figlio.valore = 99
print(f"copy: {b.figlio.valore}")      # 99
print(f"deepcopy: {c.figlio.valore}")  # 2

📌 Personalizzazione del comportamento: __copy__ e __deepcopy__

Puoi controllare il modo in cui i tuoi oggetti vengono copiati definendo metodi speciali nella loro classe.

import copy
class Personalizzata:
    def __init__(self, x):
        self.x = x
    def __copy__(self):
        print("Chiamato __copy__!")
        # Restituisce una nuova istanza, ma con lo stesso riferimento ai dati interni
        return Personalizzata(self.x)
    def __deepcopy__(self, memo):
        # memo è un dizionario per tenere traccia degli oggetti già copiati (evita la ricorsione)
        print("Chiamato __deepcopy__!")
        # Crea una copia completamente nuova, inclusi i dati annidati
        return Personalizzata(copy.deepcopy(self.x, memo))

obj = Personalizzata([1, 2])
c1 = copy.copy(obj)       # Output: Chiamato __copy__!
c2 = copy.deepcopy(obj)   # Output: Chiamato __deepcopy__!

⚡ Prestazioni e insidie

  • Velocità: copy.copy() è significativamente più veloce perché non attraversa ricorsivamente l’intera struttura dei dati. copy.deepcopy() può essere molto lento per oggetti grandi e profondamente annidati.
  • Oggetti non copiabili: Le risorse di sistema come file aperti, socket o stream non possono essere copiate. Tentare di farlo solleverà un errore.
  • Riferimenti ciclici: deepcopy è abbastanza intelligente da gestire i riferimenti ciclici (dove un oggetto A fa riferimento a B, e B a sua volta fa riferimento ad A) senza cadere in un ciclo infinito.

Quando usare cosa: Le regole principali

  1. Usa una copia superficiale (copy.copy()) se il tuo oggetto contiene solo dati immutabili o se vuoi intenzionalmente che la copia e l’originale condividano oggetti annidati. È veloce ed efficiente.
  2. Usa una copia profonda (copy.deepcopy()) quando hai bisogno di una copia completa e indipendente al 100% dall’originale, specialmente quando lavori con strutture dati complesse (liste, dizionari, istanze di classe).
  3. Per gli oggetti immutabili (numeri, stringhe, tuple senza elementi mutabili), la copia è generalmente inutile — Python ottimizza già il loro uso.
  4. Ricorda che puoi personalizzare il processo con __copy__ e __deepcopy__ per un controllo completo.

Leave a Reply

Your email address will not be published. Required fields are marked *