-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
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.
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
pytest
passes).flake8
on changed files to check).flake8-docstrings
and 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).