En Mon, 01 Jun 2009 00:28:12 -0300, Michael H. Goldwasser
<goldw...@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

--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to