From 1535893ec9a0f17c52cc86ff77f4875166b46306 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sat, 16 Sep 2023 21:11:24 +0200 Subject: [PATCH 1/4] Align copy module behaviour with pickle module --- Lib/copy.py | 30 ++++++++++++++++-------------- Lib/test/test_copy.py | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index 6d7bb9a111b5b4..b090691991a522 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -56,6 +56,8 @@ class Error(Exception): pass error = Error # backward compatibility +_NoValue = object() + __all__ = ["Error", "copy", "deepcopy"] def copy(x): @@ -74,20 +76,20 @@ def copy(x): # treat it as a regular class: return _copy_immutable(x) - copier = getattr(cls, "__copy__", None) - if copier is not None: + copier = getattr(cls, "__copy__", _NoValue) + if copier is not _NoValue: return copier(x) - reductor = dispatch_table.get(cls) - if reductor is not None: + reductor = dispatch_table.get(cls, _NoValue) + if reductor is not _NoValue: rv = reductor(x) else: - reductor = getattr(x, "__reduce_ex__", None) - if reductor is not None: + reductor = getattr(x, "__reduce_ex__", _NoValue) + if reductor is not _NoValue: rv = reductor(4) else: - reductor = getattr(x, "__reduce__", None) - if reductor: + reductor = getattr(x, "__reduce__", _NoValue) + if reductor is not _NoValue: rv = reductor() else: raise Error("un(shallow)copyable object of type %s" % cls) @@ -138,20 +140,20 @@ def deepcopy(x, memo=None, _nil=[]): if issubclass(cls, type): y = _deepcopy_atomic(x, memo) else: - copier = getattr(x, "__deepcopy__", None) - if copier is not None: + copier = getattr(x, "__deepcopy__", _NoValue) + if copier is not _NoValue: y = copier(memo) else: reductor = dispatch_table.get(cls) if reductor: rv = reductor(x) else: - reductor = getattr(x, "__reduce_ex__", None) - if reductor is not None: + reductor = getattr(x, "__reduce_ex__", _NoValue) + if reductor is not _NoValue: rv = reductor(4) else: - reductor = getattr(x, "__reduce__", None) - if reductor: + reductor = getattr(x, "__reduce__", _NoValue) + if reductor is not _NoValue: rv = reductor() else: raise Error( diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index c66c6eeb00811e..26eaa14f51e6f4 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -68,6 +68,26 @@ def __reduce__(self): self.assertIs(y, x) self.assertEqual(c, [1]) + def test_copy_invalid_reduction_methods(self): + class C(object): + __copy__ = None + x = C() + with self.assertRaises(TypeError): + copy.copy(x) + + class C(object): + __reduce_ex__ = None + x = C() + with self.assertRaises(TypeError): + copy.copy(x) + + class C(object): + __reduce_ex__ = copy._NoValue + __reduce__ = None + x = C() + with self.assertRaises(TypeError): + copy.copy(x) + def test_copy_reduce(self): class C(object): def __reduce__(self): From 30d6b97c1d132956d8a0cf90d3b12e49143d11db Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 16 Sep 2023 19:19:49 +0000 Subject: [PATCH 2/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 --- .../2023-09-16-19-19-47.gh-issue-93627.fkb529.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-09-16-19-19-47.gh-issue-93627.fkb529.rst diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-09-16-19-19-47.gh-issue-93627.fkb529.rst b/Misc/NEWS.d/next/Core and Builtins/2023-09-16-19-19-47.gh-issue-93627.fkb529.rst new file mode 100644 index 00000000000000..37d9d2539d79bf --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-09-16-19-19-47.gh-issue-93627.fkb529.rst @@ -0,0 +1 @@ +Update the Python copy module implementation to match the implementation of the pickle module. For objects setting reduction methods like :meth:`~object.__copy__` , :meth:`~object.__reduce_ex__` or :meth:`~object.__reduce__` to ``None``, a call to :meth:`copy.copy` or :meth:`copy.deepcopy` will result in a :exc:`TypeError`. From c97105f0012f7e8380e469e81f25b56322bf50b4 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Sat, 16 Sep 2023 21:25:57 +0200 Subject: [PATCH 3/4] align behaviour of __replace__ --- Lib/copy.py | 4 ++-- Lib/test/test_copy.py | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Lib/copy.py b/Lib/copy.py index b090691991a522..7401af15fee006 100644 --- a/Lib/copy.py +++ b/Lib/copy.py @@ -301,7 +301,7 @@ def replace(obj, /, **changes): frozen dataclasses. """ cls = obj.__class__ - func = getattr(cls, '__replace__', None) - if func is None: + func = getattr(cls, '__replace__', _NoValue) + if func is _NoValue: raise TypeError(f"replace() does not support {cls.__name__} objects") return func(obj, **changes) diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index 26eaa14f51e6f4..55d6a4fb445221 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -981,6 +981,13 @@ class C: with self.assertRaisesRegex(TypeError, 'unexpected keyword argument'): copy.replace(c, x=1, error=2) + def test_invalid_replace_method(self): + class A: + __replace__ = None + a = A() + with self.assertRaises(TypeError): + copy.replace(a) + def global_foo(x, y): return x+y From d451ba426e224942bc3d28c65008edca2338bdd6 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Wed, 11 Dec 2024 21:23:32 +0100 Subject: [PATCH 4/4] lint --- .../2023-09-16-19-19-47.gh-issue-93627.fkb529.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/{Core and Builtins => Core_and_Builtins}/2023-09-16-19-19-47.gh-issue-93627.fkb529.rst (100%) diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-09-16-19-19-47.gh-issue-93627.fkb529.rst b/Misc/NEWS.d/next/Core_and_Builtins/2023-09-16-19-19-47.gh-issue-93627.fkb529.rst similarity index 100% rename from Misc/NEWS.d/next/Core and Builtins/2023-09-16-19-19-47.gh-issue-93627.fkb529.rst rename to Misc/NEWS.d/next/Core_and_Builtins/2023-09-16-19-19-47.gh-issue-93627.fkb529.rst