Skip to content

Commit

Permalink
FIX: account for changes to cpython with copy/deepcopy to super()
Browse files Browse the repository at this point in the history
See python/cpython#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
  • Loading branch information
tacaswell committed Dec 31, 2024
1 parent 9ec6cb7 commit ba83fb4
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 3 deletions.
13 changes: 12 additions & 1 deletion lib/matplotlib/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import copy
from functools import lru_cache
import sys
from weakref import WeakValueDictionary

import numpy as np
Expand Down Expand Up @@ -282,7 +283,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

Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/path.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class Path:
def readonly(self) -> bool: ...
def copy(self) -> Path: ...
def __deepcopy__(self, memo: dict[int, Any] | None = ...) -> Path: ...
deepcopy = __deepcopy__
def deepcopy(self) -> Path: ...

@classmethod
def make_compound_path_from_polys(cls, XY: ArrayLike) -> Path: ...
Expand Down
8 changes: 7 additions & 1 deletion lib/matplotlib/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import copy
import functools
import itertools
import sys
import textwrap
import weakref
import math
Expand Down Expand Up @@ -141,7 +142,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 = {}
Expand Down

0 comments on commit ba83fb4

Please sign in to comment.