copy - Duplica Oggetti
Scopo: Fornisce funzioni per la duplicazione di oggetti utilizzando la semantica shallow (Copia per Indirizzo) oppure deep (Copia in Profondità)
Il modulo copy include due funzioni, copy()
e deepcopy()
per la duplicazione di oggetti esistenti.
Copia per Indirizzo (Shallow)
La copia shallow creata da copy()
è un nuovo contenitore popolato con riferimenti al contenuto dell'oggetto originale. Quando si esegue una copia shallow di un oggetto list
, viene costruito un nuovo oggetto list
, che viene popolato con gli elementi dell'oggetto originale.
# copy_shallow.py
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
a = MyClass('a')
my_list = [a]
dup = copy.copy(my_list)
print(' my_list:', my_list)
print(' dup:', dup)
print(" dup e' my_list:", (dup is my_list))
print(' dup == my_list:', (dup == my_list))
print("dup[0] e' my_list[0]:", (dup[0] is my_list[0]))
print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))
Per una copia shallow, l'istanza di MyClass
non viene duplicata, quindi il riferimento nella lista duplicata dup
è allo stesso oggetto referenziato da my_list
.
$ python3 copy_shallow.py my_list: [<__main__.MyClass object at 0x7f81e6b81be0>] dup: [<__main__.MyClass object at 0x7f81e6b81be0>] dup e' my_list: False dup == my_list: True dup[0] e' my_list[0]: True dup[0] == my_list[0]: True
Copie in Profondità (Deep)
La copia deep creata da deepcopy()
è un nuovo contenitore popolato con copia del contenuto dell'oggetto originale. Per effettuare una copia deep di una list
, viene costruito un nuovo oggetto list
e gli elementi della lista originale vengono copiati, poi dette copie vengono aggiunte alla nuova list
.
La sostituzione della chiamata a copy()
con deepcopy()
rende evidente la differenza nell'output.
# copy_deep.py
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
a = MyClass('a')
my_list = [a]
dup = copy.deepcopy(my_list)
print(' my_list:', my_list)
print(' dup:', dup)
print(" dup e' my_list:", (dup is my_list))
print(' dup == my_list:', (dup == my_list))
print("dup[0] e' my_list[0]:", (dup[0] is my_list[0]))
print('dup[0] == my_list[0]:', (dup[0] == my_list[0]))
Il primo elemento della lista non rappresenta più lo stesso riferimento all'oggetto tuttavia quando i due oggetti vengono confrontati vengono ancora considerati come uguali.
$ python3 copy_deep.py my_list: [<__main__.MyClass object at 0x7f0542029be0>] dup: [<__main__.MyClass object at 0x7f0542029cf8>] dup e' my_list: False dup == my_list: True dup[0] e' my_list[0]: False dup[0] == my_list[0]: True
Personalizzare il Comportamento di Copia
Per controllare come vengono effettuate le copie si utilizzano i metodi speciali __copy__()
e __deepcopy__()
.
__copy()
viene chiamato senza argomenti a dovrebbe ritornare una copia shallow dell'oggetto.__deepcopy()__
viene chiamato con un dizionario memo, e dovrebbe ritornare una copia deep dell'oggetto. Qualsiasi attributo membro che si vuole copiare dovrebbe essere passato acopy.deepcopy()
assieme al dizionario memo, per controllare la ricorsione. (Il dizionario memo viene spiegato successivamente con maggior dettaglio).
Questo esempio illustra come i metodi vengono chiamati:
# copy_hooks.py
import copy
import functools
@functools.total_ordering
class MyClass:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __gt__(self, other):
return self.name > other.name
def __copy__(self):
print('__copy__()')
return MyClass(self.name)
def __deepcopy__(self, memo):
print('__deepcopy__({})'.format(memo))
return MyClass(copy.deepcopy(self.name, memo))
a = MyClass('a')
sc = copy.copy(a)
dc = copy.deepcopy(a)
Il dizionario memo viene utilizzato per tenere traccia dei valori che sono già stati copiati, per evitare una ricorsione infinita.
$ python3 copy_hooks.py __copy__() __deepcopy__({})
Ricorsione nella Copia Profonda (Deep)
Per evitare problemi che possono verificarsi nella copia di strutture dati ricorsive, deepcopy()
utilizza un dizionario per tracciare gli oggetti che sono già stati copiati. Questo dizionario viene passato al metodo __deepcopy__()
in modo che possa essere esaminato.
L'esempio seguente mostra come una struttura dati interconnessa come un Grafo diretto possa contribuire alla protezione contro la ricorsione implementando un metodo __deepcopy__()
.
# copy_recursion.py
import copy
class Graph:
def __init__(self, name, connections):
self.name = name
self.connections = connections
def add_connection(self, other):
self.connections.append(other)
def __repr__(self):
return 'Graph(name={}, id={})'.format(
self.name, id(self))
def __deepcopy__(self, memo):
print('\nChiamata di __deepcopy__ per {!r}'.format(self))
if self in memo:
existing = memo.get(self)
print(' Già copiato in {!r}'.format(existing))
return existing
print(' Dizionario Memo:')
if memo:
for k, v in memo.items():
print(' {}: {}'.format(k, v))
else:
print(' (vuoto)')
dup = Graph(copy.deepcopy(self.name, memo), [])
print(' In copia al nuovo oggetto {}'.format(dup))
memo[self] = dup
for c in self.connections:
dup.add_connection(copy.deepcopy(c, memo))
return dup
root = Graph('root', [])
a = Graph('a', [root])
b = Graph('b', [a, root])
root.add_connection(a)
root.add_connection(b)
dup = copy.deepcopy(root)
La classe Graph
include due metodi basici per un grafo diretto. Una istanza può essere inizializzata con un nome ed una lista di nodi esistenti ai quali è connesso. Il metodo add_connection()
viene usato per impostare connessioni bidirezionali. Viene anche usato dall'operatore di copia profonda.
Il metodo __deepcopy__()
stampa messaggi per mostrare come esso viene chiamato, e gestisce i contenuti del dizionario memo alla bisogna. Invece di copiare la lista di connessioni brutalmente, crea una nuova lista ed aggiunge ad essa copie delle connessioni individuali. Questo assicura che il dizionario memo venga aggiornato mano a mano che un nuovo nodo viene duplicato, ed evita di incoorere in ricorsioni o copie supplementari dei nodi. Come in precedenza, ritorna l'oggetto copiato quando ha finito.
.
Ci sono parecchi cicli nel grafo mostrato nella figura qui sopra, ma la gestione della ricorsione con il dizionario memo previene l'attraversamento dal generare un errore di stack overflow. Quando il nodo root viene copiato, l'output è:
$ python3 copy_recursion.py Chiamata di __deepcopy__ per Graph(name=root, id=140049081420824) Dizionario Memo: (vuoto) In copia al nuovo oggetto Graph(name=root, id=140049081420936) Chiamata di __deepcopy__ per Graph(name=a, id=140049081420880) Dizionario Memo: Graph(name=root, id=140049081420824): Graph(name=root, id=140049081420936) In copia al nuovo oggetto Graph(name=a, id=140049081530128) Chiamata di __deepcopy__ per Graph(name=root, id=140049081420824) Già copiato in Graph(name=root, id=140049081420936) Chiamata di __deepcopy__ per Graph(name=b, id=140049081421048) Dizionario Memo: 140049081420824: Graph(name=root, id=140049081420936) Graph(name=root, id=140049081420824): Graph(name=root, id=140049081420936) 140049059317640: [Graph(name=root, id=140049081420824), Graph(name=a, id=140049081420880)] 140049081420880: Graph(name=a, id=140049081530128) Graph(name=a, id=140049081420880): Graph(name=a, id=140049081530128) In copia al nuovo oggetto Graph(name=b, id=140049081530240)
La seconda volta che il nodo root viene incontrato, mentre il nodo a viene copiato, __deepcopy__()
rileva la ricorsione e riutilizza il valore esistente recuperandolo dal dizionario memo invece di creare un nuovo oggetto.
Vedere anche:
- copy
- La documentazione della libreria standard per questo modulo.