-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Improved implementation of Path.copy and deepcopy #20731
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
Conversation
The original Path.copy raised RecursionError, new implementation mimicks deepcopy, creating shared members. Path tests were updated to utilize member functions copy and deepcopy instead of builtin copy functional protocol.
lib/matplotlib/path.py
Outdated
| try: | ||
| codes = self.codes | ||
| except AttributeError: | ||
| codes = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to check for attributeerror, codes is always there (it can be None though; the AttributeError in deepcopy is to handle the codes is None case).
Actually I think the default implementation of __copy__ will just work, i.e. removing the implementation of __copy__ and just having def copy(x): return copy.copy(x) would work? And likewise for __deepcopy__?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that copy is redundant and copy.copy will work, it just didn't in previous case because it was in copy call what caused infinite recurrence. On the other hand __deepcopy__ implementation is kind of optimized, isn't it? I mean that it is not recurrently calling deepcopy on each of its members as default implementation should. However, custom __deepcopy__ implementation can backfire, when we add important members and it is more code to maintain.
To conclude, there are two implementations of __deepcopy__ in the sources, and three implementations of __copy__. The other __deepcopy__ implementation is for TransformNode class and sorts out descendants (without reading through the details I assume that it implements design requirements of the whole Transform system). The other __copy__ implementations are for TransformNode class again and Colormap class, where it seems legitimate (again meeting some external requirements).
I don't see any heavy lifting done in the Path copy functions, therefore I suggest removing __copy__, __deepcopy__, and optionally the members: Path.copy(), Path.deepcopy(), and their tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure the optimization of deepcopy really matters here. So I agree with removing __copy__ and __deepcopy__; copy and deepcopy on the other hand can stay as convenience methods (plus it avoids breaking backcompat gratuitiously, and there's precedent for copy methods existing for convenience (e.g. on list or np.ndarray), and leaving the tests for copy and deepcopy also verify (somewhat) that using the default impls of __copy__ and __deepcopy__ is indeed semantically correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have found out that __deepcopy__ implementation cannot be done by means of copy.deepcopy, the reason is even stated in the docstring:
The `Path` will not be readonly, even if the source `Path` is.
Some tests relies on this behavior, one example from test_artist.py :
@image_comparison(["clip_path_clipping"], remove_text=True)
def test_clipping():
exterior = mpath.Path.unit_rectangle().deepcopy()
exterior.vertices *= 4
exterior.vertices -= 2
...
The Path from mpath.Path.unit_rectangle() is readonly, but its deepcopy isn't.
I think that at this point I should just fix the shallow copy code, which throws exceptions and keep __deepcopy__ as it is, since it has some consequences.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, sure enough.
lib/matplotlib/tests/test_path.py
Outdated
| copy.deepcopy(path2) | ||
| path1_copy = path1.deepcopy() | ||
| path2_copy = path2.deepcopy() | ||
| assert(path1 is not path1_copy) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no parentheses around the assert
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in f765d04.
anntzer
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modulo ci.
PR Summary
The original Path.copy raised RecursionError, the new implementation mimicks deepcopy, but Path members are shared.
Path tests were updated to utilize member functions copy and deepcopy instead of builtin copy functional protocol.
PR Checklist
pytestpasses).flake8on changed files to check).flake8-docstringsand runflake8 --docstring-convention=all).doc/users/next_whats_new/(follow instructions in README.rst there).doc/api/next_api_changes/(follow instructions in README.rst there).