[Tutor] Challenge supporting custom deepcopy with inheritance

Gabriel Genellina gagsl-py2 at yahoo.com.ar
Mon Jun 1 05:01:40 EDT 2009


En Mon, 01 Jun 2009 00:28:12 -0300, Michael H. Goldwasser
<goldwamh at slu.edu> escribió:

> Hi Kent,
>
> Thanks for your thoughts.
>
> For the original example code, you are correct that we could make it
> work by having B provide the one-arg constructor to match A's
> constructor signature.  But I don't see this as a general solution.
>
> Having B.__deepcopy__() make a call to A.__deepcopy__() assumes that A
> supports __deepcopy__ either direclty or indirectly.  This is not
> guaranteed as __deepcopy__ is not supported all the way back to the
> object base class.  It could be that the author of A has guaranteed
> that the syntax deepcopy(instanceA) is properly supported, but that
> could be done by other means without an explicit __deepcopy__ (such as
> relying on a true deep copy, or using getstate/setstate to affect both
> pickling and deepcopy).

In general, you have to know whether A implements __deepcopy__ or not.  
This is a small price for the flexibility (or anarchy) in the copy/pickle  
interfases: there are several ways to implement the same thing, and you  
have to know which one your base class has chosen in order to extend it.

The following is a possible implementation that doesn't use __init__ at  
all, so their different signature is not an issue:

      # class A:
      def __deepcopy__(self, memo={}):
          dup = type(self).__new__(type(self))
          dup.__aTag = self.__aTag
          dup.__aList = copy.deepcopy(self.__aList, memo)
          dup.__aList.reverse()
          return dup

      # class B:
      def __deepcopy__(self, memo={}):
          dup = A.__deepcopy__(self, memo)
          dup.__bTag = self.__bTag
          dup.__bList = copy.deepcopy(self.__bList, memo)
          return dup

Note that A.__deepcopy__ does two things: a) create a new, empty,  
instance; and b) transfer state. B.__deepcopy__ handles *its* portion of  
state only. This can be written in a more generic way, relying on  
__getstate__/__setstate__ (the methods that should return the current  
state of the object):

      # class A:
      def __deepcopy__(self, memo={}):
          dup = type(self).__new__(type(self))
          if hasattr(self, '__getstate__'): state = self.__getstate__()
          else: state = self.__dict__
          state = copy.deepcopy(state, memo)
          if hasattr(dup, '__setstate__'): dup.__setstate__(state)
          else: dup.__dict__.update(state)
          dup.__aList.reverse()
          return dup

      # remove __deepcopy__ definition from class B

Now, B (and any other subclass) is concerned only with __getstate__ /  
__setstate__, and only when the default implementation isn't appropriate.

> As another basic puzzle, consider a class definition for B where B has
> a registry list that it doesn't want cloned for the new instance (but
> it does want pickled when serialized).  This would seem to require
> that B implement its own __deepcopy__.   We want to somehow rely on
> the class definition for A to enact the cloning fo the state defined
> by A.   But without knowing about how A supports the deepcopy
> semantics, I don't see how to accomplish this goal.

I don't understand such bizarre requirement, but anyway, you can override  
__deepcopy__ (make B fix only the part that the default implementation  
doesn't implement well)

      # A.__deepcopy__ as above

      # class B:
      def __deepcopy__(self, memo={}):
          dup = A.__deepcopy__(self, memo)
          dup.__registry = self.__registry
          return dup

This [the need to know how certain feature is implemented in the base  
class] is not special or peculiar to pickle/copy, although the multiple  
(and confusing) ways in which a class can implement pickling doesn't help  
to understand the issue very well.

Consider the + operator: when some subclass wants to implement addition,  
it must know which of the several special methods involved (__add__,  
__iadd__, __radd__) are implemented in the base class, in order to  
extend/override them. Same for __cmp__/__eq__/__hash__: you can't just  
ignore what your base class implements. All of this applies to other  
languages too, but in Python, there is an additional consideration: the  
mere *existence* of some methods/attributes can have consequences on how  
the object behaves. In short, you can't blindly write __special__ methods.

-- 
Gabriel Genellina




More information about the Python-list mailing list