How to pickle a derived class which base class has __slots__ but does not have __setstate__/__getstate__

Hello, I have code like the following

class B1():
   __slots__ = [xxx]
   # B1 does not have setstate/getstate
   # but pickle can pickle B1 by default way.

class B2(B1):
   __slots__ = [xxx]
   # The derived chain is long

class B3(B2):
   __slots__ = [xxx]
   # But all of them can be pickle by default behavior

...

class D(Bn):
   __slots__ = [xxx]
   # D has some non trivial behavior, I need to customize pickle behavior
   def __getstate__(self):
      # How should I do here?
   def __setstate__(self, state):
     # And here?

Since class B does not defined getstate/setstate, I cannot call super().__getstate__/__setstate__

Since they have defined __slots__ I cannot pickle __dict__ simply

Since the deriving chain is long, I think it is a bad idea to define getstate/setstate for all of base class

What should I do?

The implementation of generic object pickling is contained in object.__reduce__, not get/setstate which is why you couldn’t call it. Here’s the c implementation. That actually calls Python code in copyreg to walk the MRO, and find all the slot names. The state is then produced by calling getattr() with all those names, then also saveing the __dict__ if present. That is a private function, but you could just copy it to your own code, and potentially simplify.

1 Like

Is this proper?

import pickle
import copyreg


class A():
    __slots__ = "x"

    def __init__(self):
        self.x = 1


class B(A):
    __slots__ = "y"

    def __init__(self):
        super().__init__()
        self.y = 2


class C(B):
    __slots__ = ["z"]

    def __init__(self):
        super().__init__()
        self.z = 3

    @staticmethod
    def _reconstructor(base_member, derived_member):
        self = object.__new__(C)
        for i, j in zip(copyreg._slotnames(B), base_member):
            setattr(self, i, j)
        self.z = derived_member  # Do customized things here
        return self

    def _reconstructor_args(self):
        base_member = [getattr(self, n) for n in copyreg._slotnames(B)]
        derived_member = self.z  # Do customized things here
        return base_member, derived_member

    def __reduce__(self):
        return self._reconstructor, self._reconstructor_args()


c = C()
print(c.x, c.y, c.z)
d = pickle.dumps(c)
c = pickle.loads(d)
print(c.x, c.y, c.z)

That seems like it would work, though it’s a little fragile - if the order of slot definitions changes in any way, your pickling will silently be corrupted. I’d suggest you do what CPython does, and store the state as a dict of attr names to values. Then when reconstructing you have the information to detect and or recover from a change. You could also switch back to using __get/setstate__(), it’s not like you’re using__reduce__()’s extra functionality.