Antimony
Antimony

Reputation: 39451

Python muliple deepcopy behaviors

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

Answers (3)

jdi
jdi

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

ecatmur
ecatmur

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

highBandWidth
highBandWidth

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

Related Questions