Skip to content

Commit 22b8286

Browse files
authored
Merge pull request #30217 from meeseeksmachine/auto-backport-of-pr-30198-on-v3.10.x
Backport PR #30198 on branch v3.10.x (Implement Path.__deepcopy__ avoiding infinite recursion)
2 parents a8deda9 + e8f3c5a commit 22b8286

File tree

4 files changed

+64
-9
lines changed

4 files changed

+64
-9
lines changed

lib/matplotlib/path.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,17 +276,37 @@ def copy(self):
276276
"""
277277
return copy.copy(self)
278278

279-
def __deepcopy__(self, memo=None):
279+
def __deepcopy__(self, memo):
280280
"""
281281
Return a deepcopy of the `Path`. The `Path` will not be
282282
readonly, even if the source `Path` is.
283283
"""
284284
# Deepcopying arrays (vertices, codes) strips the writeable=False flag.
285-
p = copy.deepcopy(super(), memo)
285+
cls = type(self)
286+
memo[id(self)] = p = cls.__new__(cls)
287+
288+
for k, v in self.__dict__.items():
289+
setattr(p, k, copy.deepcopy(v, memo))
290+
286291
p._readonly = False
287292
return p
288293

289-
deepcopy = __deepcopy__
294+
def deepcopy(self, memo=None):
295+
"""
296+
Return a deep copy of the `Path`. The `Path` will not be readonly,
297+
even if the source `Path` is.
298+
299+
Parameters
300+
----------
301+
memo : dict, optional
302+
A dictionary to use for memoizing, passed to `copy.deepcopy`.
303+
304+
Returns
305+
-------
306+
Path
307+
A deep copy of the `Path`, but not readonly.
308+
"""
309+
return copy.deepcopy(self, memo)
290310

291311
@classmethod
292312
def make_compound_path_from_polys(cls, XY):

lib/matplotlib/path.pyi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ class Path:
4444
@property
4545
def readonly(self) -> bool: ...
4646
def copy(self) -> Path: ...
47-
def __deepcopy__(self, memo: dict[int, Any] | None = ...) -> Path: ...
48-
deepcopy = __deepcopy__
47+
def __deepcopy__(self, memo: dict[int, Any]) -> Path: ...
48+
def deepcopy(self, memo: dict[int, Any] | None = None) -> Path: ...
4949

5050
@classmethod
5151
def make_compound_path_from_polys(cls, XY: ArrayLike) -> Path: ...

lib/matplotlib/tests/test_path.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,15 +355,49 @@ def test_path_deepcopy():
355355
# Should not raise any error
356356
verts = [[0, 0], [1, 1]]
357357
codes = [Path.MOVETO, Path.LINETO]
358-
path1 = Path(verts)
359-
path2 = Path(verts, codes)
358+
path1 = Path(verts, readonly=True)
359+
path2 = Path(verts, codes, readonly=True)
360360
path1_copy = path1.deepcopy()
361361
path2_copy = path2.deepcopy()
362362
assert path1 is not path1_copy
363363
assert path1.vertices is not path1_copy.vertices
364+
assert_array_equal(path1.vertices, path1_copy.vertices)
365+
assert path1.readonly
366+
assert not path1_copy.readonly
364367
assert path2 is not path2_copy
365368
assert path2.vertices is not path2_copy.vertices
369+
assert_array_equal(path2.vertices, path2_copy.vertices)
366370
assert path2.codes is not path2_copy.codes
371+
assert_array_equal(path2.codes, path2_copy.codes)
372+
assert path2.readonly
373+
assert not path2_copy.readonly
374+
375+
376+
def test_path_deepcopy_cycle():
377+
class PathWithCycle(Path):
378+
def __init__(self, *args, **kwargs):
379+
super().__init__(*args, **kwargs)
380+
self.x = self
381+
382+
p = PathWithCycle([[0, 0], [1, 1]], readonly=True)
383+
p_copy = p.deepcopy()
384+
assert p_copy is not p
385+
assert p.readonly
386+
assert not p_copy.readonly
387+
assert p_copy.x is p_copy
388+
389+
class PathWithCycle2(Path):
390+
def __init__(self, *args, **kwargs):
391+
super().__init__(*args, **kwargs)
392+
self.x = [self] * 2
393+
394+
p2 = PathWithCycle2([[0, 0], [1, 1]], readonly=True)
395+
p2_copy = p2.deepcopy()
396+
assert p2_copy is not p2
397+
assert p2.readonly
398+
assert not p2_copy.readonly
399+
assert p2_copy.x[0] is p2_copy
400+
assert p2_copy.x[1] is p2_copy
367401

368402

369403
def test_path_shallowcopy():

lib/matplotlib/transforms.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
# `np.minimum` instead of the builtin `min`, and likewise for `max`. This is
3636
# done so that `nan`s are propagated, instead of being silently dropped.
3737

38-
import copy
3938
import functools
4039
import itertools
4140
import textwrap
@@ -141,7 +140,9 @@ def __setstate__(self, data_dict):
141140
for k, v in self._parents.items() if v is not None}
142141

143142
def __copy__(self):
144-
other = copy.copy(super())
143+
cls = type(self)
144+
other = cls.__new__(cls)
145+
other.__dict__.update(self.__dict__)
145146
# If `c = a + b; a1 = copy(a)`, then modifications to `a1` do not
146147
# propagate back to `c`, i.e. we need to clear the parents of `a1`.
147148
other._parents = {}

0 commit comments

Comments
 (0)