Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Make all transforms copiable (and thus scales, too).#19281
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -16,6 +16,7 @@ | ||
import matplotlib.colorbar as mcolorbar | ||
import matplotlib.cbook as cbook | ||
import matplotlib.pyplot as plt | ||
import matplotlib.scale as mscale | ||
from matplotlib.testing.decorators import image_comparison | ||
@@ -1320,3 +1321,17 @@ def test_2d_to_rgba(): | ||
rgba_1d = mcolors.to_rgba(color.reshape(-1)) | ||
rgba_2d = mcolors.to_rgba(color.reshape((1, -1))) | ||
assert rgba_1d == rgba_2d | ||
def test_norm_deepcopy(): | ||
norm = mcolors.LogNorm() | ||
norm.vmin = 0.0002 | ||
norm2 = copy.deepcopy(norm) | ||
assert norm2.vmin == norm.vmin | ||
assert isinstance(norm2._scale, mscale.LogScale) | ||
norm = mcolors.Normalize() | ||
timhoffm marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
norm.vmin = 0.0002 | ||
norm2 = copy.deepcopy(norm) | ||
assert isinstance(norm2._scale, mscale.LinearScale) | ||
assert norm2.vmin == norm.vmin | ||
assert norm2._scale is not norm._scale |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import copy | ||
import matplotlib.pyplot as plt | ||
from matplotlib.scale import ( | ||
LogTransform, InvertedLogTransform, | ||
@@ -210,3 +212,10 @@ def test_pass_scale(): | ||
ax.set_yscale(scale) | ||
assert ax.xaxis.get_scale() == 'log' | ||
assert ax.yaxis.get_scale() == 'log' | ||
def test_scale_deepcopy(): | ||
sc = mscale.LogScale(axis='x', base=10) | ||
sc2 = copy.deepcopy(sc) | ||
assert str(sc.get_transform()) == str(sc2.get_transform()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. As a rough check that there is no shared state, I'd add
or, if you're uncomfortable with testing private attributes:
But personally, I'd test the internal state here, because ContributorAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. yes (although this revealed the need to separately implement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. 👍 The test found an implementation flaw. It's working 😄 | ||
assert sc._transform is not sc2._transform |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import copy | ||
import numpy as np | ||
from numpy.testing import (assert_allclose, assert_almost_equal, | ||
assert_array_equal, assert_array_almost_equal) | ||
@@ -696,3 +698,41 @@ def test_lockable_bbox(locked_element): | ||
assert getattr(locked, 'locked_' + locked_element) == 3 | ||
for elem in other_elements: | ||
assert getattr(locked, elem) == getattr(orig, elem) | ||
timhoffm marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
def test_copy(): | ||
a = mtransforms.Affine2D() | ||
b = mtransforms.Affine2D() | ||
s = a + b | ||
# Updating a dependee should invalidate a copy of the dependent. | ||
s.get_matrix() # resolve it. | ||
s1 = copy.copy(s) | ||
assert not s._invalid and not s1._invalid | ||
a.translate(1, 2) | ||
assert s._invalid and s1._invalid | ||
assert (s1.get_matrix() == a.get_matrix()).all() | ||
# Updating a copy of a dependee shouldn't invalidate a dependent. | ||
s.get_matrix() # resolve it. | ||
b1 = copy.copy(b) | ||
b1.translate(3, 4) | ||
assert not s._invalid | ||
assert (s.get_matrix() == a.get_matrix()).all() | ||
def test_deepcopy(): | ||
a = mtransforms.Affine2D() | ||
b = mtransforms.Affine2D() | ||
s = a + b | ||
# Updating a dependee shouldn't invalidate a deepcopy of the dependent. | ||
s.get_matrix() # resolve it. | ||
s1 = copy.deepcopy(s) | ||
assert not s._invalid and not s1._invalid | ||
a.translate(1, 2) | ||
assert s._invalid and not s1._invalid | ||
assert (s1.get_matrix() == mtransforms.Affine2D().get_matrix()).all() | ||
# Updating a deepcopy of a dependee shouldn't invalidate a dependent. | ||
s.get_matrix() # resolve it. | ||
b1 = copy.deepcopy(b) | ||
b1.translate(3, 4) | ||
assert not s._invalid | ||
assert (s.get_matrix() == a.get_matrix()).all() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -33,6 +33,7 @@ | ||
# `np.minimum` instead of the builtin `min`, and likewise for `max`. This is | ||
# done so that `nan`s are propagated, instead of being silently dropped. | ||
import copy | ||
import functools | ||
import textwrap | ||
import weakref | ||
@@ -139,11 +140,33 @@ def __setstate__(self, data_dict): | ||
k: weakref.ref(v, lambda _, pop=self._parents.pop, k=k: pop(k)) | ||
for k, v in self._parents.items() if v is not None} | ||
def __copy__(self): | ||
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 = {} | ||
# If `c = a + b; c1 = copy(c)`, then modifications to `a` also need to | ||
# be propagated to `c1`. | ||
for key, val in vars(self).items(): | ||
if isinstance(val, TransformNode) and id(self) in val._parents: | ||
other.set_children(val) # val == getattr(other, key) | ||
return other | ||
def __deepcopy__(self, memo): | ||
# We could deepcopy the entire transform tree, but nothing except | ||
# `self` is accessible publicly, so we may as well just freeze `self`. | ||
other = self.frozen() | ||
if other is not self: | ||
return other | ||
# Some classes implement frozen() as returning self, which is not | ||
# acceptable for deepcopying, so we need to handle them separately. | ||
other = copy.deepcopy(super(), memo) | ||
# If `c = a + b; a1 = copy(a)`, then modifications to `a1` do not | ||
timhoffm marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
# propagate back to `c`, i.e. we need to clear the parents of `a1`. | ||
other._parents = {} | ||
# If `c = a + b; c1 = copy(c)`, this creates a separate tree | ||
timhoffm marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
# (`c1 = a1 + b1`) so nothing needs to be done. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. OK, so with a deepcopy the tranform is frozen, or at least to the point where someone would need to dig out the children? Does the | ||
return other | ||
def invalidate(self): | ||
""" | ||