From 2ade6e9694a75723bf256c3f363338bd6bfc60fc Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 5 Sep 2024 21:49:34 +0200 Subject: [PATCH 1/4] Improve performance of deepcopy by avoiding exception in the memo handling --- Lib/copy.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index a79976d3a658f0..6d50bfd3aa509a 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -116,7 +116,7 @@ def _copy_immutable(x): del d, t -def deepcopy(x, memo=None, _nil=[]): +def deepcopy(x, memo=None): """Deep copy operation on arbitrary Python objects. See the module's __doc__ string for more info. @@ -130,9 +130,10 @@ def deepcopy(x, memo=None, _nil=[]): d = id(x) if memo is None: memo = {} + memo[id(memo)] = [] else: - y = memo.get(d, _nil) - if y is not _nil: + y = memo.get(d, None) + if y is not None: return y copier = _deepcopy_dispatch.get(cls) @@ -168,7 +169,7 @@ def deepcopy(x, memo=None, _nil=[]): # If is its own copy, don't memoize. if y is not x: memo[d] = y - _keep_alive(x, memo) # Make sure x lives at least as long as d + memo[id(memo)].append(x) # Make sure x lives at least as long as d return y _atomic_types = {types.NoneType, types.EllipsisType, types.NotImplementedType, @@ -218,22 +219,6 @@ def _deepcopy_method(x, memo): # Copy instance methods del d -def _keep_alive(x, memo): - """Keeps a reference to the object x in the memo. - - Because we remember objects by their id, we have - to assure that possibly temporary objects are kept - alive by referencing them. - We store a reference at the id of the memo, which should - normally not be used unless someone tries to deepcopy - the memo itself... - """ - try: - memo[id(memo)].append(x) - except KeyError: - # aha, this is the first one :-) - memo[id(memo)]=[x] - def _reconstruct(x, memo, func, args, state=None, listiter=None, dictiter=None, *, deepcopy=deepcopy): From 579da4381da3067e108b2107553dd66c12b30c5e Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Thu, 5 Sep 2024 22:00:44 +0200 Subject: [PATCH 2/4] Update tests --- Lib/test/test_copy.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index 3dec64cc9a2414..785d5fa8b9cfeb 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -429,12 +429,14 @@ def test_deepcopy_reflexive_dict(self): def test_deepcopy_keepalive(self): memo = {} + memo[id(memo)] = [] x = [] y = copy.deepcopy(x, memo) self.assertIs(memo[id(memo)][0], x) def test_deepcopy_dont_memo_immutable(self): memo = {} + memo[id(memo)] = [] x = [1, 2, 3, 4] y = copy.deepcopy(x, memo) self.assertEqual(y, x) @@ -442,6 +444,7 @@ def test_deepcopy_dont_memo_immutable(self): self.assertEqual(len(memo), 2) memo = {} + memo[id(memo)] = [] x = [(1, 2)] y = copy.deepcopy(x, memo) self.assertEqual(y, x) From 32c93b12ee1a5708258fc75e3b34d4c04b39094f Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 21:29:24 +0000 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-05-23-21-29-17.gh-issue-123746.dGpBeY.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-05-23-21-29-17.gh-issue-123746.dGpBeY.rst diff --git a/Misc/NEWS.d/next/Library/2025-05-23-21-29-17.gh-issue-123746.dGpBeY.rst b/Misc/NEWS.d/next/Library/2025-05-23-21-29-17.gh-issue-123746.dGpBeY.rst new file mode 100644 index 00000000000000..c9f39d82aa1827 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-23-21-29-17.gh-issue-123746.dGpBeY.rst @@ -0,0 +1 @@ +Improve performance :meth:`copy.deepcopy` setting keep alive key on the memo construction. From 86d0ec255ce9651336c8d3972ed0211efa76ce05 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 5 Dec 2025 19:47:15 +0100 Subject: [PATCH 4/4] use ellipsis as sentinel for deepcopy memo --- Lib/copy.py | 5 ++--- Lib/test/test_copy.py | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index d910b5494723f3..11e60afbb90854 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -120,8 +120,7 @@ def deepcopy(x, memo=None): d = id(x) if memo is None: - memo = {} - memo[id(memo)] = [] + memo = {...: []} else: y = memo.get(d, None) if y is not None: @@ -160,7 +159,7 @@ def deepcopy(x, memo=None): # If is its own copy, don't memoize. if y is not x: memo[d] = y - memo[id(memo)].append(x) # Make sure x lives at least as long as d + memo[...].append(x) # Make sure x lives at least as long as d return y _atomic_types = frozenset({types.NoneType, types.EllipsisType, types.NotImplementedType, diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index dfbcb7176b5309..00e4d44d6ca770 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -435,14 +435,14 @@ def test_deepcopy_reflexive_dict(self): def test_deepcopy_keepalive(self): memo = {} - memo[id(memo)] = [] + memo[...] = [] x = [] y = copy.deepcopy(x, memo) - self.assertIs(memo[id(memo)][0], x) + self.assertIs(memo[...][0], x) def test_deepcopy_dont_memo_immutable(self): memo = {} - memo[id(memo)] = [] + memo[...] = [] x = [1, 2, 3, 4] y = copy.deepcopy(x, memo) self.assertEqual(y, x) @@ -450,7 +450,7 @@ def test_deepcopy_dont_memo_immutable(self): self.assertEqual(len(memo), 2) memo = {} - memo[id(memo)] = [] + memo[...] = [] x = [(1, 2)] y = copy.deepcopy(x, memo) self.assertEqual(y, x)