[Python-checkins] gh-103000: Optimise dataclasses asdict/astuple for common types (#103005)

AlexWaygood webhook-mailer at python.org
Mon Apr 10 17:51:07 EDT 2023


https://github.com/python/cpython/commit/d034590294d4618880375a6db513c30bce3e126b
commit: d034590294d4618880375a6db513c30bce3e126b
branch: main
author: David Ellis <ducksual at gmail.com>
committer: AlexWaygood <Alex.Waygood at Gmail.com>
date: 2023-04-10T22:50:58+01:00
summary:

gh-103000: Optimise dataclasses asdict/astuple for common types (#103005)

Co-authored-by: Carl Meyer <carl at oddbird.net>
Co-authored-by: Alex Waygood <Alex.Waygood at Gmail.com>

files:
A Misc/NEWS.d/next/Library/2023-03-24-20-49-48.gh-issue-103000.6eVNZI.rst
M Lib/dataclasses.py

diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py
index 7558287bad44..4026c8b77975 100644
--- a/Lib/dataclasses.py
+++ b/Lib/dataclasses.py
@@ -222,6 +222,29 @@ def __repr__(self):
 # https://bugs.python.org/issue33453 for details.
 _MODULE_IDENTIFIER_RE = re.compile(r'^(?:\s*(\w+)\s*\.)?\s*(\w+)')
 
+# Atomic immutable types which don't require any recursive handling and for which deepcopy
+# returns the same object. We can provide a fast-path for these types in asdict and astuple.
+_ATOMIC_TYPES = frozenset({
+    # Common JSON Serializable types
+    types.NoneType,
+    bool,
+    int,
+    float,
+    str,
+    # Other common types
+    complex,
+    bytes,
+    # Other types that are also unaffected by deepcopy
+    types.EllipsisType,
+    types.NotImplementedType,
+    types.CodeType,
+    types.BuiltinFunctionType,
+    types.FunctionType,
+    type,
+    range,
+    property,
+})
+
 # This function's logic is copied from "recursive_repr" function in
 # reprlib module to avoid dependency.
 def _recursive_repr(user_function):
@@ -1291,7 +1314,9 @@ class C:
 
 
 def _asdict_inner(obj, dict_factory):
-    if _is_dataclass_instance(obj):
+    if type(obj) in _ATOMIC_TYPES:
+        return obj
+    elif _is_dataclass_instance(obj):
         result = []
         for f in fields(obj):
             value = _asdict_inner(getattr(obj, f.name), dict_factory)
@@ -1363,7 +1388,9 @@ class C:
 
 
 def _astuple_inner(obj, tuple_factory):
-    if _is_dataclass_instance(obj):
+    if type(obj) in _ATOMIC_TYPES:
+        return obj
+    elif _is_dataclass_instance(obj):
         result = []
         for f in fields(obj):
             value = _astuple_inner(getattr(obj, f.name), tuple_factory)
diff --git a/Misc/NEWS.d/next/Library/2023-03-24-20-49-48.gh-issue-103000.6eVNZI.rst b/Misc/NEWS.d/next/Library/2023-03-24-20-49-48.gh-issue-103000.6eVNZI.rst
new file mode 100644
index 000000000000..15f16d9eb4c1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-03-24-20-49-48.gh-issue-103000.6eVNZI.rst
@@ -0,0 +1,2 @@
+Improve performance of :func:`dataclasses.astuple` and
+:func:`dataclasses.asdict` in cases where the contents are common Python types.



More information about the Python-checkins mailing list