diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index dc31d403b837ad..78342f809c11be 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1991,19 +1991,14 @@ def test_pickle(self): if type(fd) == FrozenDictSlots: fd.slot_attr = 456 with self.subTest(fd=fd, proto=proto): - if proto >= 2: - p = pickle.dumps(fd, proto) - fd2 = pickle.loads(p) - self.assertEqual(fd2, fd) - self.assertEqual(type(fd2), type(fd)) - if type(fd) == FrozenDict: - self.assertEqual(fd2.attr, 123) - if type(fd) == FrozenDictSlots: - self.assertEqual(fd2.slot_attr, 456) - else: - # protocol 0 and 1 don't support frozendict - with self.assertRaises(TypeError): - pickle.dumps(fd, proto) + p = pickle.dumps(fd, proto) + fd2 = pickle.loads(p) + self.assertEqual(fd2, fd) + self.assertEqual(type(fd2), type(fd)) + if type(fd) == FrozenDict: + self.assertEqual(fd2.attr, 123) + if type(fd) == FrozenDictSlots: + self.assertEqual(fd2.slot_attr, 456) def test_pickle_iter(self): fd = frozendict(c=1, b=2, a=3, d=4, e=5, f=6) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-07-18-19-51.gh-issue-155324.Pk4Rz7.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-07-18-19-51.gh-issue-155324.Pk4Rz7.rst new file mode 100644 index 00000000000000..3eba2262dbe733 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-07-18-19-51.gh-issue-155324.Pk4Rz7.rst @@ -0,0 +1,3 @@ +``frozendict`` can now be pickled with all pickle protocols, matching +:class:`frozenset`. Previously protocols 0 and 1 raised :exc:`TypeError`. +Patch by tonghuaroot. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index c650aa456d2cc9..f7caa48e66d993 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -8344,14 +8344,29 @@ _PyObject_InlineValuesConsistencyCheck(PyObject *obj) // --- frozendict implementation --------------------------------------------- static PyObject * -frozendict_getnewargs(PyObject *op, PyObject *Py_UNUSED(dummy)) +frozendict_reduce(PyObject *op, PyObject *Py_UNUSED(dummy)) { - // Call dict(op): convert 'op' frozendict to a dict + // Mirror frozenset.__reduce__ so frozendict pickles at every protocol + // (__getnewargs__ alone only works at protocol 2 and above). + // Call dict(op) to convert the frozendict to a dict argument. PyObject *arg = PyObject_CallOneArg((PyObject*)&PyDict_Type, op); if (arg == NULL) { return NULL; } - return Py_BuildValue("(N)", arg); + PyObject *args = PyTuple_Pack(1, arg); + Py_DECREF(arg); + if (args == NULL) { + return NULL; + } + PyObject *state = _PyObject_GetState(op); + if (state == NULL) { + Py_DECREF(args); + return NULL; + } + PyObject *result = PyTuple_Pack(3, Py_TYPE(op), args, state); + Py_DECREF(args); + Py_DECREF(state); + return result; } @@ -8377,7 +8392,8 @@ static PyMethodDef frozendict_methods[] = { DICT___REVERSED___METHODDEF {"__class_getitem__", Py_GenericAlias, METH_O|METH_CLASS, PyDoc_STR("frozendicts are generic over two types, signifying (respectively) the types of the frozendict's keys and values")}, - {"__getnewargs__", frozendict_getnewargs, METH_NOARGS}, + {"__reduce__", frozendict_reduce, METH_NOARGS, + PyDoc_STR("Return state information for pickling.")}, {NULL, NULL} /* sentinel */ };