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
- Switch to protocol >= 2 where slot state is handled automatically
- Override `__getstate__`/`__setstate__` on the class to expose slot values explicitly
- 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
- Override __getstate__ on slotted classes that get pickled
- Upgrade protocol constants across the codebase to >= 2
- Add regression tests for legacy-protocol consumers of slotted models
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
- cannot pickle {cls.__name__!r} object: a class that defines
- cannot pickle {cls.__name__!r} object
- reduction functions must be callable
- constructors must be callable
- can't serialize %s
AI-assisted analysis of RustPython/RustPython@25e76af1ed (2026-08-17).
Data as JSON: /api/errors/26b438cc942dde3e.
Report an issue: GitHub.