From 113d02adf1d8a99a7400851d70253c3dd6c19c16 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 31 Dec 2024 15:08:31 -0500 Subject: [PATCH 1/3] MNT: use copy.deepcopy to implement deepcopy method We provide deepcopy and copy methods as a shortcut for users to avoid having to import the copy module and call the required function. Directly calling `__deepcopy__` side-steps all of the memoization machinery and may lead to incorrect results. --- lib/matplotlib/path.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index a021706fb1e5..9c3f50320636 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -285,7 +285,12 @@ def __deepcopy__(self, memo=None): p._readonly = False return p - deepcopy = __deepcopy__ + def deepcopy(self, memo=None): + """ + Return a shallow copy of the `Path`, which will share the + vertices and codes with the source `Path`. + """ + return copy.deepcopy(self, memo=memo) @classmethod def make_compound_path_from_polys(cls, XY): From 42874f95233eefcae0b5564ef752898d31dd37ea Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 31 Dec 2024 15:16:13 -0500 Subject: [PATCH 2/3] FIX: account for changes to cpython with copy/deepcopy to super() See https://github.com/python/cpython/issues/126817 for upstream discussion. This works around the change by using (private) methods from the copy module to re-implement the path though copy/deepcopy that we would like to use but avoid the special-casing for `super()` objects that is breaking us. We could vendor the current versions of `_keep_alive` (weakref work to manage lifecycles) and `_reconstruct` (where the recursion happens) to superficially avoid using private functions from CPython. However, if these functions do change significantly I worry that our copies would not inter-operate anyway. Closes #29157 --- lib/matplotlib/path.py | 13 ++++++++++++- lib/matplotlib/transforms.py | 8 +++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index 9c3f50320636..1660d090e318 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -11,6 +11,7 @@ import copy from functools import lru_cache +import sys from weakref import WeakValueDictionary import numpy as np @@ -281,7 +282,17 @@ def __deepcopy__(self, memo=None): readonly, even if the source `Path` is. """ # Deepcopying arrays (vertices, codes) strips the writeable=False flag. - p = copy.deepcopy(super(), memo) + if sys.version_info >= (3, 14): + from copy import _reconstruct, _keep_alive + rv = super().__reduce_ex__(4) + assert memo is not None + p = _reconstruct(self, memo, *rv) + if memo is not None: + memo[id(self)] = p + _keep_alive(self, memo) + + else: + p = copy.deepcopy(super(), memo) p._readonly = False return p diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index 2cca56f04457..f874184affae 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -38,6 +38,7 @@ import copy import functools import itertools +import sys import textwrap import weakref import math @@ -140,7 +141,12 @@ def __setstate__(self, data_dict): for k, v in self._parents.items() if v is not None} def __copy__(self): - other = copy.copy(super()) + if sys.version_info >= (3, 14): + from copy import _reconstruct + rv = super().__reduce_ex__(4) + other = _reconstruct(self, None, *rv) + else: + other = copy.copy(super()) # If `c = a + b; a1 = copy(a)`, then modifications to `a1` do not # propagate back to `c`, i.e. we need to clear the parents of `a1`. other._parents = {} From 717ea248bd1cedda6e18336afa7879f4372e8324 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 9 Jan 2025 13:41:32 -0500 Subject: [PATCH 3/3] DOC: correct deepcopy docstring --- lib/matplotlib/path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index 1660d090e318..e4a7403ceecb 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -298,7 +298,7 @@ def __deepcopy__(self, memo=None): def deepcopy(self, memo=None): """ - Return a shallow copy of the `Path`, which will share the + Return a deep copy of the `Path`, with copies of the vertices and codes with the source `Path`. """ return copy.deepcopy(self, memo=memo)