Problem using copy.copy with my own class

George Sakkis george.sakkis at gmail.com
Thu Apr 24 00:41:18 EDT 2008


On Apr 23, 9:48 pm, Jeffrey Barish <jeff_bar... at earthlink.net> wrote:
>
> Here it is:
>
> import copy
>
> class Test(int):
>     def __new__(cls, arg1, arg2):
>         return int.__new__(cls, arg1)
>
>     def __init__(self, arg1, arg2):
>         self.arg2 = arg2
>
> if __name__ == '__main__':
>     t = Test(0, 0)
>     t_copy = copy.copy(t)

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.

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:

class Test(int):
    ...
    def __copy__(self):
        return Test(int(self), self.arg2)

The copy.copy() function looks for this special method and invokes it
if it's defined. 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.

> Traceback (most recent call last):
>   File "copytest.py", line 12, in <module>
>     t_copy = copy.copy(t)
>   File "/usr/lib/python2.5/copy.py", line 95, in copy
>     return _reconstruct(x, rv, 0)
>   File "/usr/lib/python2.5/copy.py", line 322, in _reconstruct
>     y = callable(*args)
>   File "/usr/lib/python2.5/copy_reg.py", line 92, in __newobj__
>     return cls.__new__(cls, *args)
> TypeError: __new__() takes exactly 3 arguments (2 given)

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:

class Test(int):
    def __new__(cls, arg1, arg2=None):
        return int.__new__(cls, arg1)

    # don't need to define __copy__ now

from copy import copy
t = Test(0, 0)
assert copy(t) == t

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.

George

[1] http://docs.python.org/lib/module-copy.html
[2] http://docs.python.org/lib/node320.html



More information about the Python-list mailing list