From d034590294d4618880375a6db513c30bce3e126b Mon Sep 17 00:00:00 2001 From: David Ellis Date: Mon, 10 Apr 2023 22:50:58 +0100 Subject: gh-103000: Optimise dataclasses asdict/astuple for common types (#103005) Co-authored-by: Carl Meyer Co-authored-by: Alex Waygood --- Lib/dataclasses.py | 31 ++++++++++++++++++++-- .../2023-03-24-20-49-48.gh-issue-103000.6eVNZI.rst | 2 ++ 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-03-24-20-49-48.gh-issue-103000.6eVNZI.rst diff --git a/Lib/dataclasses.py b/Lib/dataclasses.py index 7558287..4026c8b 100644 --- a/Lib/dataclasses.py +++ b/Lib/dataclasses.py @@ -222,6 +222,29 @@ _POST_INIT_NAME = '__post_init__' # 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 @@ def asdict(obj, *, dict_factory=dict): 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 @@ def astuple(obj, *, tuple_factory=tuple): 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 0000000..15f16d9 --- /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. -- cgit v0.12