Reputation: 39451
Suppose I have two classes, say Manager
and Graph
, where each Graph has a reference to its manager, and each Manager has references to a collection of graphs that it owns. I want to be able to do two things
1) Copy a graph, which performs a deepcopy except that the new graph references the same manager as the old one.
2) Copy a manager, which creates a new manager and also copies all the graphs it owns.
What is the best way to do this? I don't want to have to roll my own deepcopy implementation, but the standard copy.deepcopy
doesn't appear to provide this level of flexibility.
Upvotes: 2
Views: 1108
Reputation: 92567
What about something really simple like defining the copy protocol for the class?
import copy
class Graph(object):
def __init__(self):
self.data = [1,2,3]
self.manager = None
def __getstate__(self):
return {
'data': self.data,
'manager': self.manager
}
def __setstate__(self, state):
self.manager = state.pop('manager')
for name, val in state.iteritems():
setattr(self, name, copy.copy(val))
class Manager(object):
def __init__(self):
self.data = [4,5,6]
self.graphs = []
def __getstate__(self):
return {
'data': self.data,
'graphs': self.graphs
}
def __setstate__(self, state):
self.graphs = [copy.copy(g) for g in state.pop('graphs')]
for name, val in state.iteritems():
setattr(self, name, copy.copy(val))
You would just define the manager to be a reference and everything else to get copied the way you want.
Example:
In [2]: m1 = Manager()
In [3]: g1 = Graph()
In [4]: g1.manager = m1
In [5]: g2 = copy.copy(g1)
In [6]: g2.manager is g1.manager
Out[6]: True
In [7]: g2.data is g1.data
Out[7]: False
In [8]: m1.graphs.extend([g1,g2])
In [9]: m2 = copy.copy(m1)
In [10]: m2.data is m1.data
Out[10]: False
In [11]: m2.graphs[0] is m1.graphs[0]
Out[11]: False
In [12]: m2.graphs[0].manager is m1.graphs[0].manager
Out[12]: True
Upvotes: 0
Reputation: 157374
You can easily do this by looking at the memodict
passed to the __deepcopy__
special method:
class Graph(object):
def __init__(self, manager=None):
self.manager = None if manager is None else weakref.ref(manager)
def __deepcopy__(self, memodict):
manager = self.manager()
return Graph(memodict.get(id(manager), manager))
class Manager(object):
def __init__(self, graphs=[]):
self.graphs = graphs
for g in self.graphs:
g.manager = weakref.ref(self)
I'm assuming here that you're using weakref.ref
to break the cycle between Graph
and Manager
; if you're using something else then adjust as appropriate.
>>> m = Manager([Graph(), Graph()])
>>> mc = copy.deepcopy(m)
>>> [g.manager() is mc for g in mc.graphs]
[True, True]
>>> copy.deepcopy(m.graphs[0]).manager() is m
True
Upvotes: 3
Reputation: 17321
If there are no other objects referenced in graph (just simple fields), then copy.copy(graph)
should make a copy, while copy.deepcopy(manager)
should copy the manager and its graphs, assuming there is a list such as manager.graphs
.
But in general you are right, the copy
module does not have this flexibility, and for slightly fancy situations you'd probably need to roll your own.
Upvotes: 1