Challenge supporting custom deepcopy with inheritance

Michael H. Goldwasser goldwamh at slu.edu
Sun May 31 01:27:26 EDT 2009


I've been playing around recently with customizing the deepcopy
semantics within an inheritance hierarchy, and run across the
following hurdle.

Assume that class B inherits from class A, and that class A has
legitimately customized its deepcopy semantics (but in a way that is
not known to class B).  If we want a deepcopy of B to be defined so
that relevant state inherited from A is copied as would be done for
class A, and with B able to control how to deepcopy the extra state
that it introduces.  I cannot immediately find a general way to
properly implement the deepcopy of B.

To make the discussion tangible, I include an outrageously artificial
example in the code fragment below.  In this code, class A implements
__deepcopy__ to intentionally create a clone that has a reversed
version of a list instance.  The beginning of the main test
demonstrates that an instance of A can successfully be cloned with this
semantics.  But the attempt to deepcopy an instance of B fails (the
current failure is because B has not implemented __deepcopy__ and the
inherited version of A.__deepcopy__ assumes the wrong constructor
signature).

The question is how class B should defined so that its instances can
"inherit" this deepcopy semantic.  Ideally, I'd like to find a recipe
for class B that works regardless of how A accomplishes its semantics.
That is, I can find ways to rewrite A to be more supportive of B's
goal, but I'd like to know if there is a general solution that could
be used if the author of B doesn't have control over A's
implementation.

Here are some initial thoughts:

 * I'm only concerned with single inheritance (thankfully)

 * If A had relied on use of __getstate__ and __setstate__ to affect
   the desired change, then the inheritance works as desired.  But I
   have an application where I want to leave the default pickling
   behavior alone, and only to change what happens with clones.

 * If we did not use inheritance, but instead had a class B that had
   an A instance in its state, this is easy. We would call deepcopy
   on the A instance to get an acceptable copy of that instance for
   the new B.  But in the example below, we insist on B being a
   subtype of A.

 * If A.__deepcopy__ had started
            dup = A(self.__aTag)
   rather than
            dup = self.__class__(self.__aTag)

   then the following perverse approach for B.__deepcopy__ succeeds:

        def __deepcopy__(self, memo={}):
            tempBTag = self.__bTag
            tempBList = self.__bList
            del self.__bTag     # hide these attributes to avoid risk
            del self.__bList    # that they affect A.__deepcopy__
            dupA = A.__deepcopy__(self, memo)
            self.__bTag = tempBTag
            self.__bList = tempBList
            dup = B.__new__(B)
            memo[id(self)] = dup
            dup.__dict__ = dupA.__dict__
            dup.__bTag = copy.deepcopy(self.__bTag, memo)
            dup.__bList = copy.deepcopy(self.__bList, memo)
            return dup

    but this again presumes that we have some control over the
    strategy employed by class A, it relies on the fact that
    A.__deepcopy__ succeeded when the first parameter was actually an
    instance of class B rather than class A, and it seems like a
    perverse strategy on the whole, even if it does succeed on this
    example.  Also, there could be a flaw n that dupA may recursively
    have included a reference back to that instance, yet we'd need
    such a reference to be to dup not dupA in the end.

I'm hoping that I've just missed a cleaner way to do this.
I welcome any comments, discussion, or suggestions.

With regard,
Michael

-----------------------------------------------------------
import copy

class A(object):
    def __init__(self, aTag):
        self.__aTag = aTag
        self.__aList = []

    def addA(self, val):
        self.__aList.append(val)

    def __repr__(self):
        return (
            "aTag: " + self.__aTag +
            "\naList ("+str(id(self.__aList))+"): " + repr(self.__aList)
            )

    def __deepcopy__(self, memo={}):
        dup = self.__class__(self.__aTag)
        memo[id(self)] = dup
        dup.__aList = copy.deepcopy(self.__aList, memo)
        dup.__aList.reverse()
        return dup

class B(A):
    def __init__(self, aTag, bTag):
        A.__init__(self, aTag)
        self.__bTag = bTag
        self.__bList = []

    def addB(self, val):
        self.__bList.append(val)

    def __repr__(self):
        return (
            A.__repr__(self) +
            "\nbTag: " + self.__bTag +
            "\nbList ("+str(id(self.__bList))+"): " + repr(self.__bList)
            )

    #  How can B support a deepcopy that provides deepcopy of bTag and
    #  bList while getting the semantics of A's deepcopy for
    #  attributes defined by A?


if __name__ == '__main__':
    print "Test of class A\n==============="
    foo = A('alice')
    foo.addA(1)
    foo.addA(2)

    bar = copy.deepcopy(foo)

    print "\nOriginal Foo:"
    print foo
    print "\nOriginal (deepcopy) Bar:"
    print bar

    bar.addA(0)

    print "\n(mutated) Bar:"
    print bar
    print "Original Foo:"
    print foo

    print "Test of class B\n==============="
    foo = B('alice', 'bob')
    foo.addA(1)
    foo.addA(2)
    foo.addB('hello')
    foo.addB('goodbye')

    bar = copy.deepcopy(foo)     #  crashes

    print "\nOriginal Foo:"
    print foo
    print "\nOriginal (deepcopy) Bar:"
    print bar

    bar.addA(0)
    bar.addB('adios')

    print "\n(mutated) Bar:"
    print bar
    print "Original Foo:"
    print foo
-----------------------------------------------------------



More information about the Python-list mailing list