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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy