Copy with __slots__
Alex Martelli
aleax at aleax.it
Wed Sep 18 09:16:28 EDT 2002
Griebel, Peer wrote [I'm only leaving the relevant parts of the script]:
> class C1(object):
> __slots__ = "s1";
>
> class C2(C1):
> __slots__ = "s2";
Funny use of semicolons here. I'd suggest omitting them!
> o2 = C2()
>
> import copy
> p2 = copy.copy(o2)
> print p2.s2 # attribute s2 does not exist (has not been copied)
>
> The last print statement fails with the an AttributeError. Since
Traceback (most recent call last):
File "sloco.py", line 25, in ?
p2 = copy.copy(o2)
File "/usr/local/lib/python2.2/copy.py", line 84, in copy
y = _reconstruct(x, reductor(), 0)
File "/usr/local/lib/python2.2/copy_reg.py", line 68, in _reduce
dict = getstate()
TypeError: a class that defines __slots__ without defining __getstate__
cannot be pickled
This is with Python 2.2.1 -- as you see, it doesn't get anywhere
as far as the print statement. The error message is somewhat
peculiar (who's trying to _pickle_ anything...?) -- and if you
do try to define __getstate__ (and possibly __setstate__), you'll
see they're no use for copy.copy (nor copy.deepcopy). _reconstruct
gets in the way.
If you only care about copy.copy, the simplest alternative is:
class C2(C1):
__slots__ = "s2"
def __copy__(self):
x = self.__class__()
x.s1 = self.s1
x.s2 = self.s2
return x
You can of course also add try/except AttributeError around each of the
assignments "x.sN = self.sN", if you want to let an instance of C2 be
copied even when one or both of its attributes are not yet defined.
For deepcopy, you could use a similar special method __deepcopy__ (needs
to deepcopy the attributes, of course).
The way to cover copy.copy, copy.deepcopy AND pickle is to have the
class supply a special method __reduce__. __reduce__ can return a
pair where the first item is callable, the second item is the tuple
to pass as arguments to the first. Thus, for example:
class C2(C1):
__slots__ = "s2"
def _build(s1, s2):
x = C2()
x.s1 = s1
x.s2 = s2
return x
_build = staticmethod(_build)
def __reduce__(self):
return C2._build, (self.s1, self.s2)
Here, _build need not worry about deep-copying -- if needed, it's
been performed previously, on the args _build gets passed.
The nicest thing about it is that a class with slots will often
already supply some nice function to build instances of the class
given values to put in the slots -- you don't necessarily have
to write factory functions such as _build just for the purpose
of supporting copy, deepcopy and pickle... you often have them
lying around!-). For example, a more typical case might be:
class C2(C1):
__slots__ = 's2'
def __init__(self, s1=None, s2=None):
C1.__init__(self, s1)
self.s2 = s2
def __reduce__(self):
return self.__class__, (self.s1, self.s2)
Here I'm assuming C1 has a similar __init__, but that's a detail.
The point is that __reduce__ can use class C2 itself as the
first item of its result -- the class IS callable, after all.
I'm specifically using self.__class__ rather than a literal C2
here as a tiny enhancement -- if a class C3 subclasses C2 and
does not override __init__ in a way that requires arguments
different from the slots' values, then C3 need not override
__reduce__ either -- it can just inherit it from C2. Not the
end of the world, but sometimes handy.
Alex
More information about the Python-list
mailing list