RustPython/RustPython · error · TypeError

a class that defines __slots__ without defining __getstate__

Error message

a class that defines __slots__ without defining __getstate__ cannot be pickled

What it means

The companion branch of `copyreg._reduce_ex` (protocols 0 and 1): when the instance does have a `__getstate__` attribute but it is the inherited default `object.__getstate__`, and the class defines `__slots__`, the old protocols still cannot carry slot state, so TypeError is raised. It is the same constraint as the protocol-numbered message, hit through the 3.11+-style default `object.__getstate__` path.

Source

Thrown at crates/vm/Lib/core_modules/copyreg.py:93

            raise TypeError(f"cannot pickle {cls.__name__!r} object")
        state = base(self)
    args = (cls, base, state)
    try:
        getstate = self.__getstate__
    except AttributeError:
        if getattr(self, "__slots__", None):
            raise TypeError(f"cannot pickle {cls.__name__!r} object: "
                            f"a class that defines __slots__ without "
                            f"defining __getstate__ cannot be pickled "
                            f"with protocol {proto}") from None
        try:
            dict = self.__dict__
        except AttributeError:
            dict = None
    else:
        if (type(self).__getstate__ is object.__getstate__ and
            getattr(self, "__slots__", None)):
            raise TypeError("a class that defines __slots__ without "
                            "defining __getstate__ cannot be pickled")
        dict = getstate()
    if dict:
        return _reconstructor, args, dict
    else:
        return _reconstructor, args

# Helper for __reduce_ex__ protocol 2

def __newobj__(cls, *args):
    return cls.__new__(cls, *args)

def __newobj_ex__(cls, args, kwargs):
    """Used by pickle protocol 4, instead of __newobj__ to allow classes with
    keyword-only arguments to be pickled correctly.
    """
    return cls.__new__(cls, *args, **kwargs)

View on GitHub (pinned to 25e76af1ed)

Solutions

  1. Switch to protocol >= 2 where slot state is handled automatically
  2. Override `__getstate__`/`__setstate__` on the class to expose slot values explicitly
  3. Audit any code path that calls `pickle.dumps` without a protocol argument and then downgrades it to 0/1

Example fix

# before
class Packet:
    __slots__ = ('src', 'dst')
    def __init__(self, src, dst): self.src, self.dst = src, dst
pickle.dumps(p, protocol=1)  # TypeError: a class that defines __slots__ ...

# after
class Packet:
    __slots__ = ('src', 'dst')
    def __init__(self, src, dst): self.src, self.dst = src, dst
    def __getstate__(self): return {'src': self.src, 'dst': self.dst}
    def __setstate__(self, st): self.src, self.dst = st['src'], st['dst']
pickle.dumps(p, protocol=1)
Defensive patterns

Strategy: validation

Validate before calling

import pickle
def slotted_class_needs_getstate(cls) -> bool:
    return bool(getattr(cls, '__slots__', None)) and \
        getattr(cls, '__getstate__', None) is object.__getstate__
if slotted_class_needs_getstate(type(obj)):
    obj_bytes = pickle.dumps(obj, protocol=max(proto, 2))

Type guard

def is_slotted_without_own_getstate(cls) -> bool:
    """True when cls defines __slots__ but inherits the default object.__getstate__."""
    return bool(getattr(cls, '__slots__', None)) and \
        cls.__dict__.get('__getstate__') is None

Try / catch

try:
    blob = pickle.dumps(obj, protocol=1)
except TypeError as e:
    if '__slots__' in str(e) and '__getstate__' in str(e):
        blob = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)
    else:
        raise

Prevention

When it happens

Trigger: `pickle.dumps(obj, protocol=0 or 1)` for a slotted class on a runtime where `object.__getstate__` exists, so attribute lookup succeeds and the inherited-default check fires.

Common situations: Slotted classes or `@dataclass(slots=True)` models pickled by legacy protocol-0/1 consumers; portability layers that assumed default `__getstate__` makes old pickles work.

Related errors


AI-assisted analysis of RustPython/RustPython@25e76af1ed (2026-08-17). Data as JSON: /api/errors/26b438cc942dde3e. Report an issue: GitHub.