| Scopo | Fornisce funzioni per la duplicazione di oggetti usando semantiche di deep e shallow copy |
| Versione Python | 1.4 |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per il modulo copy
Il modulo
copy
include due funzioni,
copy()
e
deepcopy()
per la duplicazione di oggetti esistenti.
La copia shallow creata da copy() è un nuovo contenitore riempito con i riferimenti al contenuto dell'oggetto originale. Ad esempio una nuova lista viene costruita e ad essa sono aggiunti gli elementi della lista originale.
import copy
class MyClass:
def __init__(self, name):
self.name = name
def __cmp__(self, other):
return cmp(self.name, other.name)
a = MyClass('a')
l = [ a ]
dup = copy.copy(l)
print 'l :', l
print 'dup:', dup
print 'dup è l:', (dup is l)
print 'dup == l:', (dup == l)
print 'dup[0] è l[0]:', (dup[0] is l[0])
print 'dup[0] == l[0]:', (dup[0] == l[0])
Per una copia shallow l'istanza di MyClass non viene duplicata quindi il riferimento nella lista di dup è lo stesso oggetto che si trova nella lista l.
$ python copy_shallow.py l : [<__main__.MyClass instance at 0x8c648>] dup: [<__main__.MyClass instance at 0x8c648>] dup è l: False dup == l: True dup[0] è l[0]: True dup[0] == l[0]: True
La copia deep creata da deepcopy() è un nuovo contenitore riempito con copie del contenuto dell'oggetto originale. Ad esempio viene costrutita una nuova lista e gli elementi della lista originale vengono copiati, quindi le copie sono aggiunte alla nuova lista.
Sostituendo la chiamata a copy() con quella a deepcopy() la differenza diventa evidente.
dup = copy.deepcopy(l)
Notare che il primo elemento della lista non è più un riferimento allo stesso oggetto, ma i due oggetti risultano comunque uguali.
$ python copy_deep.py l : [<__main__.MyClass instance at 0x8c670>] dup: [<__main__.MyClass instance at 0x8c788>] dup è l: False dup == l: True dup[0] è l[0]: False dup[0] == l[0]: True
E' possibilie controllare in che modo le copie sono effettuate tramite gli agganci di __copy__ e __deepcopy__.
Questo esempio dimostra come vengono chiamati i metodi.
import copy
class MyClass:
def __init__(self, name):
self.name = name
def __cmp__(self, other):
return cmp(self.name, other.name)
def __copy__(self):
print '__copy__()'
return MyClass(self.name)
def __deepcopy__(self, memo):
print '__deepcopy__(%s)' % str(memo)
return MyClass(copy.deepcopy(self.name, memo))
a = MyClass('a')
sc = copy.copy(a)
dc = copy.deepcopy(a)
$ python copy_hooks.py
__copy__()
__deepcopy__({})
Per evitare problemi con la duplicazione di strutture dati recursive, {sbk}deepcopy(){ebk} usa un dizionario per tenere traccia degli oggetti che sono già stati copiati. Questo dizionario viene passato al metodo {sbk}__deepcopy__(){ebk} così che possa essere da esso usato
Questo esempio mostra come una struttura dati interconnessa come un Digraph possa essere utilizzata come protezione contro la recursione implementando il metodo {sbk}__deepcopy__(){ebk}. Questo particolare esempio è solo a scopi illustrativi, visto che la implementazione predefinita di {sbk}deepcopy(){ebk} già gestisce correttamente i casi di recursione.
import copy
import pprint
class Graph:
def __init__(self, name, connections):
self.name = name
self.connections = connections
def addConnection(self, other):
self.connections.append(other)
def __repr__(self):
return '<Graph(%s) id=%s>' % (self.name, id(self))
def __deepcopy__(self, memo):
print
print repr(self)
not_there = []
existing = memo.get(self, not_there)
if existing is not not_there:
print ' GIA\' COPIATO IN', repr(existing)
return existing
pprint.pprint(memo, indent=4, width=40)
dup = Graph(copy.deepcopy(self.name, memo), [])
print ' COPIA VERSO', repr(dup)
memo[self] = dup
for c in self.connections:
dup.addConnection(copy.deepcopy(c, memo))
return dup
root = Graph('root', [])
a = Graph('a', [root])
b = Graph('b', [a, root])
root.addConnection(a)
root.addConnection(b)
dup = copy.deepcopy(root)
Per prima cosa alcuni metodi base di graph. graph può essere inizializzato con un nome ed una lista di nodi esistenti ai quali è connesso. Il metodo addConnection() viene usato per impostare connessioni bidirezionali. Viene anche usato dall'operatore di deepcopy.
Il metodo {sbk}__deepcopy__() stampa messaggi per mostrare come è stato chiamato, e gestisce i contenuti del dizionario memo come richiesto. Invece di copiare brutalmente la lista di connessione, crea una nuova lista ed aggiunge le copie delle connessioni individuali ad essa. Questo assicura che il dizionario memo sia aggiornato e che ogni nodo venga duplicato, evitando problemi di recursione o copie aggiuntive dei nodi. Come in precedenza, restituisce l'oggetto copiato quando ha finito.
Successivamente possiamo impostare un graph con nodi radice, a, b. Gli estremi sono a->root, b->a, b->root, root->, root->b
Quando il nodo root viene copiato osserviamo:
$ python copy_recursion.py
<Graph(root) id=580624>
{ }
COPYING TO <Graph(root) id=804104>
<Graph(a) id=803784>
{ <Graph(root) id=580624>: <Graph(root) id=804104>,
565472: 'root',
571120: ['root']}
COPIA VERSO <Graph(a) id=804064>
<Graph(root) id=580624>
GIA' COPIATI IN <Graph(root) id=804104>
<Graph(b) id=803824>
{ <Graph(root) id=580624>: <Graph(root) id=804104>,
<Graph(a) id=803784>: <Graph(a) id=804064>,
290848: 'a',
565472: 'root',
571120: [ 'root',
'a',
<Graph(root) id=580624>,
<Graph(a) id=803784>],
580624: <Graph(root) id=804104>,
803784: <Graph(a) id=804064>}
COPIA VERSO <Graph(b) id=807360>
Notare che la seconda volta che viene esaminato il nodo root, mentre il nodo a viene copiato, la recursione viene riconosciuta e viene usata la copia esistente in luogo di una nuova.
Vedere anche: