{"record":{"id":"26b438cc942dde3e","repo":"RustPython/RustPython","slug":"a-class-that-defines-slots-without-defining","errorCode":null,"errorMessage":"a class that defines __slots__ without defining __getstate__ cannot be pickled","messagePattern":"a class that defines __slots__ without defining __getstate__ cannot be pickled","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"crates/vm/Lib/core_modules/copyreg.py","lineNumber":93,"sourceCode":"            raise TypeError(f\"cannot pickle {cls.__name__!r} object\")\n        state = base(self)\n    args = (cls, base, state)\n    try:\n        getstate = self.__getstate__\n    except AttributeError:\n        if getattr(self, \"__slots__\", None):\n            raise TypeError(f\"cannot pickle {cls.__name__!r} object: \"\n                            f\"a class that defines __slots__ without \"\n                            f\"defining __getstate__ cannot be pickled \"\n                            f\"with protocol {proto}\") from None\n        try:\n            dict = self.__dict__\n        except AttributeError:\n            dict = None\n    else:\n        if (type(self).__getstate__ is object.__getstate__ and\n            getattr(self, \"__slots__\", None)):\n            raise TypeError(\"a class that defines __slots__ without \"\n                            \"defining __getstate__ cannot be pickled\")\n        dict = getstate()\n    if dict:\n        return _reconstructor, args, dict\n    else:\n        return _reconstructor, args\n\n# Helper for __reduce_ex__ protocol 2\n\ndef __newobj__(cls, *args):\n    return cls.__new__(cls, *args)\n\ndef __newobj_ex__(cls, args, kwargs):\n    \"\"\"Used by pickle protocol 4, instead of __newobj__ to allow classes with\n    keyword-only arguments to be pickled correctly.\n    \"\"\"\n    return cls.__new__(cls, *args, **kwargs)\n","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/RustPython/RustPython/blob/25e76af1ed305aed0f3c3a5e3ab7fe00bb5487b1/crates/vm/Lib/core_modules/copyreg.py#L75-L111","documentation":"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.","triggerScenarios":"`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.","commonSituations":"Slotted classes or `@dataclass(slots=True)` models pickled by legacy protocol-0/1 consumers; portability layers that assumed default `__getstate__` makes old pickles work.","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"],"exampleFix":"# before\nclass Packet:\n    __slots__ = ('src', 'dst')\n    def __init__(self, src, dst): self.src, self.dst = src, dst\npickle.dumps(p, protocol=1)  # TypeError: a class that defines __slots__ ...\n\n# after\nclass Packet:\n    __slots__ = ('src', 'dst')\n    def __init__(self, src, dst): self.src, self.dst = src, dst\n    def __getstate__(self): return {'src': self.src, 'dst': self.dst}\n    def __setstate__(self, st): self.src, self.dst = st['src'], st['dst']\npickle.dumps(p, protocol=1)","handlingStrategy":"validation","validationCode":"import pickle\ndef slotted_class_needs_getstate(cls) -> bool:\n    return bool(getattr(cls, '__slots__', None)) and \\\n        getattr(cls, '__getstate__', None) is object.__getstate__\nif slotted_class_needs_getstate(type(obj)):\n    obj_bytes = pickle.dumps(obj, protocol=max(proto, 2))","typeGuard":"def is_slotted_without_own_getstate(cls) -> bool:\n    \"\"\"True when cls defines __slots__ but inherits the default object.__getstate__.\"\"\"\n    return bool(getattr(cls, '__slots__', None)) and \\\n        cls.__dict__.get('__getstate__') is None","tryCatchPattern":"try:\n    blob = pickle.dumps(obj, protocol=1)\nexcept TypeError as e:\n    if '__slots__' in str(e) and '__getstate__' in str(e):\n        blob = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)\n    else:\n        raise","preventionTips":["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"],"tags":["pickle","copyreg","slots","getstate","protocol","typeerror"],"backgroundTag":"pickle-slots-no-getstate","analyzedSha":"25e76af1ed305aed0f3c3a5e3ab7fe00bb5487b1","analyzedAt":"2026-08-17T08:14:46.185Z","contentChangedAt":"2026-08-17T08:14:46.185Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}