-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
FUTURE BUG: reconsider how we deep-copy path objects #29157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Good first issue - notes for new contributorsThis issue is suited to new contributors because it does not require understanding of the We do not assign issues. Check the Development section in the sidebar for linked pull If something is unclear, please reach out on any of our communication |
Hi! I'm relatively a beginner but I'd love to take this on as a challenge if it's okay 😊 I was wondering, would it be acceptable to replace p = copy.deepcopy(super(), memo) with p = self.__class__.__new__(self.__class__) and to manually copy the attributes like p._vertices = copy.deepcopy(self._vertices, memo)
p._codes = copy.deepcopy(self._codes, memo)
p._interpolation_steps = self._interpolation_steps
p._readonly = False ? The end result would be like def __deepcopy__(self, memo=None):
"""
Return a deepcopy of the `Path`. The `Path` will not be
readonly, even if the source `Path` is.
"""
if id(self) in memo:
return memo[id(self)]
p = self.__class__.__new__(self.__class__)
memo[id(self)] = p
p._vertices = copy.deepcopy(self._vertices, memo)
p._codes = copy.deepcopy(self._codes, memo)
p._interpolation_steps = self._interpolation_steps
p._readonly = False
return p |
@tacaswell cls = self.__class__
new_instance = cls.__new__(cls)
# Add the new instance to the memo dictionary to handle circular references
if memo is None:
memo = {}
memo[id(self)] = new_instance
# Deepcopy the attributes explicitly
new_instance.vertices = copy.deepcopy(self.vertices, memo)
new_instance.codes = copy.deepcopy(self.codes, memo)
new_instance._readonly = False
return new_instance This is because in the original code, By explicitly utilizing the |
@saikarna913 Are you working with @kyracho ? I am a bit confused by you posting an almost identical comment. Reaching for It is probably also worth reading #21280 for the context of why we are using super() (along with the bug it fixed and the alternative PR we declined). |
Without looking at the proper fix, it feels like we could/should get rid of the TextPath class and just have a |
|
I apologize for my confusion. I'm trying to clarify something. Since there isn't a deepcopy method in Textpath, it inherits from its parent class(PATH), which calls super() in the deepcopy method. Currently, the method that invoked super() is Textpath, so it will follow the method resolution order (MRO) of Textpath. In this MRO, the parent class is PATH, meaning that super() effectively returns a copy of path to deepcopy. However, this doesn't seem to be creating a true deepcopy of Textpath. Could you help me understand how this code functions? Thank you! why can't we explicitly define deepcopy for subclasses like Textpath? |
It seems simple enough to do |
What do you mean by this?
There is no technical reason we can not, but the behavior we want is "do the normal deepcopy thing, but set writable" which is a behavior inherited from |
@tacaswell and regarding the deepcopy I think this works in path.py file def __deepcopy__(self, memo):
# Create a shallow copy of the object first.
new_instance = copy.copy(self)
# Deep copy the _vertices array and set the writeable flag based on the readonly flag
new_instance._vertices = np.copy(self._vertices)
new_instance._vertices.flags.writeable = not self._readonly
# Deep copy the _codes array if it exists, and handle the writeable flag
if self._codes is not None:
new_instance._codes = np.copy(self._codes)
new_instance._codes.flags.writeable = not self._readonly
# Deep copy any additional attributes that were added in the class
for key, value in self.__dict__.items():
# Skip internal attributes (e.g., _vertices, _codes, _readonly)
if key in ['_vertices', '_codes', '_readonly']:
continue
# If the attribute is a mutable object, perform deep copy, otherwise just copy it
if isinstance(value, (np.ndarray, list, dict)):
new_instance.__dict__[key] = copy.deepcopy(value, memo)
else:
new_instance.__dict__[key] = value
# Retain the readonly flag
new_instance._readonly = self._readonly
return new_instance in textpath.py def __deepcopy__(self, memo):
# Create a shallow copy of the TextPath object
new_instance = super().__deepcopy__(memo)
# Deep copy text-specific attributes
new_instance._xy = self._xy
new_instance._size = self._size
new_instance._should_simplify = self._should_simplify
# If cached vertices exist, we need to copy them and ensure the writeable flag
if self._cached_vertices is not None:
new_instance._cached_vertices = np.copy(self._cached_vertices)
new_instance._cached_vertices.flags.writeable = not self._readonly
# Mark the object as invalid since it's a deep copy
new_instance._invalid = True
return new_instance |
I'm sure with a bit of refinement those methods would work, but I am not sure that mostly re-implementing As an implementation detail, can you explain why you went with |
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 matplotlib#29157
I've put up a PR that fixes this and a related issue. |
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 matplotlib#29157
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 matplotlib#29157
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 matplotlib#29157
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 matplotlib#29157
We currently use
super()
in the__deepcopy__
implementation ofPath
matplotlib/lib/matplotlib/path.py
Lines 279 to 287 in 183b04f
however, on the main branch of CPython this causes infinite recursion (python/cpython#126817). Although upstream may (or may not) sort out how to fix this, we should see if there is another way we can do what we need to do and avoid any fallout from changes in CPython.
Good first issue because "make sure deepcopy works on Paths" is a narrowly limited task, but hard because it will require understanding why we currently use
super()
and enough of the deepcopy/pickle protocol details to fix it.attn @anntzer
The text was updated successfully, but these errors were encountered: