Improved implementation of Path.copy and deepcopy#20731
Improved implementation of Path.copy and deepcopy#20731timhoffm merged 2 commits intomatplotlib:masterfrom
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.
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.
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.
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.
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.
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.
no parentheses around the assert
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).