Problem using copy.copy with my own class

Jeffrey Barish jeff_barish at earthlink.net
Thu Apr 24 18:17:35 CEST 2008


George Sakkis wrote:

> First off, inheriting from a basic builtin type such as int and
> changing its constructor's signature is not typical; you should
> rethink your design unless you know what you're doing.

Nah, I would never claim to know what I'm doing.  However, I have to say
that I have been finding this technique very useful.  When I started
developing this program, I used an int.  Then I discovered that I needed to
have a string associated with the int.  By subclassing int to add a string,
I managed to make the change transparent to the code I had already written. 
Only the new code that needed the associated string knew that it was
available.  In another case, I subclassed str so that I could have a long
form for a string (e.g., a full name attached to the surname).  Are these
applications of subclassing bad form?  What is the motivation for your
warning?
 
> One way to make this work is to define the special __copy__ method
> [1], specifying explicitly how to create a copy of a Test instance:
> 
> Normally (i.e. for pure Python classes that don't
> subclass a builtin other than object) copy.copy() is smart enough to
> know how to create a copy without an explicit __copy__ method, so in
> general you don't have to define it for every class that has to be
> copyable.

Yes, I noted in my original posting (which seems to have fallen off this
thread) that the __copy__method solved the problem (at least on one
platform).  However, I was wondering why it was necessary when what I was
defining was supposedly the default action.  Thanks for your explanation.

> The traceback is not obvious indeed. It turns out it involves calling
> the arcane __reduce_ex__ special method [2] defined for int, which
> returns a tuple of 5 items; the second is the tuple
> (<class '__main__.Test'>, 0) and these are the arguments passed to
> Test.__new__. So another way of fixing it is keep Test.__new__
> compatible with int.__new__ by making optional all arguments after the
> first:

This suggestion is very interesting.  It seems to be an alternative to the
solution suggested by Gabriel.  The reference that Gabriel provided
includes the statement:

  Instances of a new-style type C are created using

  obj = C.__new__(C, *args)
 
  where args is the result of calling __getnewargs__() on the original
  object; if there is no __getnewargs__(), an empty tuple is assumed.

Gabriel's solution using __getnewargs__ assures that args receives a
non-empty tuple.  Your solution renders the empty tuple impotent by
specifying default values.  Correct me if I am wrong.

I would be interested in a translation into English of the following
statement from the same reference that Gabriel provided:

  Implementing this method [i.e., __getnewargs__] is needed if the 
  type establishes some internal invariants when the instance is 
  created, or if the memory allocation is affected by the values 
  passed to the __new__() method for the type (as it is for tuples 
  and strings).

What is an "internal invariant"?  How do I know when the memory allocation
is affected?  Does my Test class affect the memory allocation?

> As a sidenote, your class works fine without changing anything when
> pickling/unpickling instead of copying, although pickle calls
> __reduce_ex__ too:
> 
> from pickle import dumps,loads
> t = Test(0, 0)
> assert loads(dumps(t)) == t
> 
> Perhaps someone more knowledgeable can explain the subtle differences
> between pickling and copying here.

I have a situation in the full program where pickling seems to be failing in
the same manner as copy, but I have not been able yet to reproduce the
problem in a simple test program.

Thanks to all for your comments.
-- 
Jeffrey Barish




More information about the Python-list mailing list