https://github.com/python/cpython/commit/8e43f3d1177f22c95f5fc66349a3b748a36470c9
commit: 8e43f3d1177f22c95f5fc66349a3b748a36470c9
branch: main
author: Pieter Eendebak <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2026-04-22T19:39:08-07:00
summary:
gh-145056: Add support for frozendict in dataclass asdict and astuple (#145125)
files:
A Misc/NEWS.d/next/Library/2026-02-22-19-36-00.gh-issue-145056.TH8nX4.rst
M Doc/library/dataclasses.rst
M Lib/dataclasses.py
M Lib/test/test_dataclasses/__init__.py
diff --git a/Doc/library/dataclasses.rst b/Doc/library/dataclasses.rst
index fd8e0c0bea1cb1..0bce3e5b762b8b 100644
--- a/Doc/library/dataclasses.rst
+++ b/Doc/library/dataclasses.rst
@@ -371,8 +371,8 @@ Module contents
Converts the dataclass *obj* to a dict (by using the
factory function *dict_factory*). Each dataclass is converted
to a dict of its fields, as ``name: value`` pairs. dataclasses, dicts,
- lists, and tuples are recursed into. Other objects are copied with
- :func:`copy.deepcopy`.
+ frozendicts, lists, and tuples are recursed into. Other objects are copied
+ with :func:`copy.deepcopy`.
Example of using :func:`!asdict` on nested dataclasses::
@@ -402,8 +402,8 @@ Module contents
Converts the dataclass *obj* to a tuple (by using the
factory function *tuple_factory*). Each dataclass is converted
- to a tuple of its field values. dataclasses, dicts, lists, and
- tuples are recursed into. Other objects are copied with
+ to a tuple of its field values. dataclasses, dicts, frozendicts, lists,
+ and tuples are recursed into. Other objects are copied with
:func:`copy.deepcopy`.
Continuing from the previous example::
diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 0c7e01cb16b192..9d5bed6b96fc49 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -1496,7 +1496,8 @@ class C:
If given, 'dict_factory' will be used instead of built-in dict.
The function applies recursively to field values that are
dataclass instances. This will also look into built-in containers:
- tuples, lists, and dicts. Other objects are copied with 'copy.deepcopy()'.
+ tuples, lists, dicts, and frozendicts. Other objects are copied
+ with 'copy.deepcopy()'.
"""
if not _is_dataclass_instance(obj):
raise TypeError("asdict() should be called on dataclass instances")
@@ -1552,7 +1553,7 @@ def _asdict_inner(obj, dict_factory):
return obj_type(*[_asdict_inner(v, dict_factory) for v in obj])
else:
return obj_type(_asdict_inner(v, dict_factory) for v in obj)
- elif issubclass(obj_type, dict):
+ elif issubclass(obj_type, (dict, frozendict)):
if hasattr(obj_type, 'default_factory'):
# obj is a defaultdict, which has a different constructor from
# dict as it requires the default_factory as its first arg.
@@ -1587,7 +1588,8 @@ class C:
If given, 'tuple_factory' will be used instead of built-in tuple.
The function applies recursively to field values that are
dataclass instances. This will also look into built-in containers:
- tuples, lists, and dicts. Other objects are copied with 'copy.deepcopy()'.
+ tuples, lists, dicts, and frozendicts. Other objects are copied
+ with 'copy.deepcopy()'.
"""
if not _is_dataclass_instance(obj):
@@ -1616,7 +1618,7 @@ def _astuple_inner(obj, tuple_factory):
# generator (which is not true for namedtuples, handled
# above).
return type(obj)(_astuple_inner(v, tuple_factory) for v in obj)
- elif isinstance(obj, dict):
+ elif isinstance(obj, (dict, frozendict)):
obj_type = type(obj)
if hasattr(obj_type, 'default_factory'):
# obj is a defaultdict, which has a different constructor from
diff --git a/Lib/test/test_dataclasses/__init__.py
b/Lib/test/test_dataclasses/__init__.py
index b44b1da0336d09..e0cfe3df3e6357 100644
--- a/Lib/test/test_dataclasses/__init__.py
+++ b/Lib/test/test_dataclasses/__init__.py
@@ -1693,17 +1693,24 @@ class GroupTuple:
class GroupDict:
id: int
users: Dict[str, User]
+ @dataclass
+ class GroupFrozenDict:
+ id: int
+ users: frozendict[str, User]
a = User('Alice', 1)
b = User('Bob', 2)
gl = GroupList(0, [a, b])
gt = GroupTuple(0, (a, b))
gd = GroupDict(0, {'first': a, 'second': b})
+ gfd = GroupFrozenDict(0, frozendict({'first': a, 'second': b}))
self.assertEqual(asdict(gl), {'id': 0, 'users': [{'name': 'Alice',
'id': 1},
{'name': 'Bob', 'id':
2}]})
self.assertEqual(asdict(gt), {'id': 0, 'users': ({'name': 'Alice',
'id': 1},
{'name': 'Bob', 'id':
2})})
- self.assertEqual(asdict(gd), {'id': 0, 'users': {'first': {'name':
'Alice', 'id': 1},
- 'second': {'name':
'Bob', 'id': 2}}})
+ expected_dict = {'id': 0, 'users': {'first': {'name': 'Alice', 'id':
1},
+ 'second': {'name': 'Bob', 'id':
2}}}
+ self.assertEqual(asdict(gd), expected_dict)
+ self.assertEqual(asdict(gfd), expected_dict)
def test_helper_asdict_builtin_object_containers(self):
@dataclass
@@ -1884,14 +1891,21 @@ class GroupTuple:
class GroupDict:
id: int
users: Dict[str, User]
+ @dataclass
+ class GroupFrozenDict:
+ id: int
+ users: frozendict[str, User]
a = User('Alice', 1)
b = User('Bob', 2)
gl = GroupList(0, [a, b])
gt = GroupTuple(0, (a, b))
gd = GroupDict(0, {'first': a, 'second': b})
+ gfd = GroupFrozenDict(0, frozendict({'first': a, 'second': b}))
self.assertEqual(astuple(gl), (0, [('Alice', 1), ('Bob', 2)]))
self.assertEqual(astuple(gt), (0, (('Alice', 1), ('Bob', 2))))
- self.assertEqual(astuple(gd), (0, {'first': ('Alice', 1), 'second':
('Bob', 2)}))
+ d = {'first': ('Alice', 1), 'second': ('Bob', 2)}
+ self.assertEqual(astuple(gd), (0, d))
+ self.assertEqual(astuple(gfd), (0, frozendict(d)))
def test_helper_astuple_builtin_object_containers(self):
@dataclass
diff --git
a/Misc/NEWS.d/next/Library/2026-02-22-19-36-00.gh-issue-145056.TH8nX4.rst
b/Misc/NEWS.d/next/Library/2026-02-22-19-36-00.gh-issue-145056.TH8nX4.rst
new file mode 100644
index 00000000000000..45be0109677cd1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-02-22-19-36-00.gh-issue-145056.TH8nX4.rst
@@ -0,0 +1 @@
+Add support for :class:`frozendict` in :meth:`dataclasses.asdict` and
:meth:`dataclasses.astuple`.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]