From f317c1b3979d1382201f369c476993b517ef2b1e Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Sun, 30 Jun 2024 03:16:49 +0100 Subject: [PATCH 1/5] Optimize deepcopy of empty containers --- Lib/copy.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index 7a1907d75494d7..0754c7dcb0c7d8 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -123,19 +123,23 @@ def deepcopy(x, memo=None, _nil=[]): cls = type(x) - if cls in _atomic_types: + if cls in _atomic_types or (cls in _immutable_iterables and len(x) == 0): return x - d = id(x) if memo is None: + if cls in _mutable_iterables and len(x) == 0: + return cls() + d = id(x) memo = {} else: + d = id(x) y = memo.get(d, _nil) if y is not _nil: return y - copier = _deepcopy_dispatch.get(cls) - if copier is not None: + if cls in _mutable_iterables and len(x) == 0: + y = cls() + elif copier := _deepcopy_dispatch.get(cls): y = copier(x, memo) else: if issubclass(cls, type): @@ -173,6 +177,8 @@ def deepcopy(x, memo=None, _nil=[]): _atomic_types = {types.NoneType, types.EllipsisType, types.NotImplementedType, int, float, bool, complex, bytes, str, types.CodeType, type, range, types.BuiltinFunctionType, types.FunctionType, weakref.ref, property} +_mutable_iterables = {list, dict, set} +_immutable_iterables = {tuple, frozenset} _deepcopy_dispatch = d = {} From 943a10fe6e62ddc07a4199a262ea0931e6fd3fb5 Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Mon, 1 Jul 2024 00:23:51 +0100 Subject: [PATCH 2/5] Remove special case for tuple and frozensets --- Lib/copy.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index 0754c7dcb0c7d8..2ffdfa886d211d 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -123,11 +123,11 @@ def deepcopy(x, memo=None, _nil=[]): cls = type(x) - if cls in _atomic_types or (cls in _immutable_iterables and len(x) == 0): + if cls in _atomic_types: return x if memo is None: - if cls in _mutable_iterables and len(x) == 0: + if cls in _builtin_iterables and len(x) == 0: return cls() d = id(x) memo = {} @@ -137,7 +137,7 @@ def deepcopy(x, memo=None, _nil=[]): if y is not _nil: return y - if cls in _mutable_iterables and len(x) == 0: + if cls in _builtin_iterables and len(x) == 0: y = cls() elif copier := _deepcopy_dispatch.get(cls): y = copier(x, memo) @@ -177,8 +177,7 @@ def deepcopy(x, memo=None, _nil=[]): _atomic_types = {types.NoneType, types.EllipsisType, types.NotImplementedType, int, float, bool, complex, bytes, str, types.CodeType, type, range, types.BuiltinFunctionType, types.FunctionType, weakref.ref, property} -_mutable_iterables = {list, dict, set} -_immutable_iterables = {tuple, frozenset} +_builtin_iterables = {tuple, list, dict, set, frozenset} _deepcopy_dispatch = d = {} From 68c589f8f34892bf29e8d951946955a6ae5537c9 Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Mon, 1 Jul 2024 00:33:05 +0100 Subject: [PATCH 3/5] Add NEWS entry --- .../next/Library/2024-07-01-00-32-33.gh-issue-121192.RZFhQP.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-07-01-00-32-33.gh-issue-121192.RZFhQP.rst diff --git a/Misc/NEWS.d/next/Library/2024-07-01-00-32-33.gh-issue-121192.RZFhQP.rst b/Misc/NEWS.d/next/Library/2024-07-01-00-32-33.gh-issue-121192.RZFhQP.rst new file mode 100644 index 00000000000000..d5437172c8dd10 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-07-01-00-32-33.gh-issue-121192.RZFhQP.rst @@ -0,0 +1,2 @@ +Add fast path to :func:`copy.deepcopy` for empty lists, tuples, dicts, sets +or frozensets. From ba7298c7ff9801ccac64a3d127b279de5b3524dc Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Mon, 1 Jul 2024 02:12:16 +0100 Subject: [PATCH 4/5] Don't call len for checking for empty container --- Lib/copy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index 2ffdfa886d211d..3e96c6a2e678c1 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -127,7 +127,7 @@ def deepcopy(x, memo=None, _nil=[]): return x if memo is None: - if cls in _builtin_iterables and len(x) == 0: + if cls in _builtin_iterables and not x: return cls() d = id(x) memo = {} @@ -137,7 +137,7 @@ def deepcopy(x, memo=None, _nil=[]): if y is not _nil: return y - if cls in _builtin_iterables and len(x) == 0: + if cls in _builtin_iterables and not x: y = cls() elif copier := _deepcopy_dispatch.get(cls): y = copier(x, memo) From d87901edccdb811075711f7a6d1651044afc8086 Mon Sep 17 00:00:00 2001 From: Lukas Geiger Date: Tue, 2 Jul 2024 20:33:28 +0100 Subject: [PATCH 5/5] Revert optimization for nested empty containers --- Lib/copy.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index 3e96c6a2e678c1..a476270d687005 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -137,9 +137,8 @@ def deepcopy(x, memo=None, _nil=[]): if y is not _nil: return y - if cls in _builtin_iterables and not x: - y = cls() - elif copier := _deepcopy_dispatch.get(cls): + copier = _deepcopy_dispatch.get(cls) + if copier is not None: y = copier(x, memo) else: if issubclass(cls, type):