-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Cleanup transforms.py. #7394
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
Cleanup transforms.py. #7394
Conversation
e1ce046
to
2def7be
Compare
|
||
def get_points(self): | ||
return NotImplementedError() | ||
raise NotImplementedError |
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.
Should this be raise NotImplementedError()
?
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.
https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement:
Otherwise, raise evaluates the first expression as the exception object. It must be either a subclass or an instance of BaseException. If it is a class, the exception instance will be obtained when needed by instantiating the class with no arguments.
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.
huh, never used it like that.
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.
raise ValueError
is pretty common? (if you're too lazy (hem hem) to write a proper error message)
""" | ||
(property) :attr:`xmin` is the left edge of the bounding box. | ||
""" | ||
return np.min(self.get_points()[:, 0]) |
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.
why np.min
over min
?
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.
So that nan
passes through -- which is probably the right thing to do? (min(np.nan, 1)
is 1, np.min(np.nan, 1)
is np.nan.) (and in fact needed for the various new implementations of the contains
/overlaps
methods)
I know you're not touching this, but is all the |
I am a tad concerned about performance regressions on this. attn @mdboom as you last touched / wrote this code in 2008. |
@story645 There's a bunch of |
git grep says the problem isn't so bad...bunch of .cpp and backend files 😅, but otherwise it's offsetbox, texmanger and transforms. |
return self.get_points()[:, 1] | ||
intervaly = property(_get_intervaly, None, None, """ | ||
@property | ||
def intervaly(self): |
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.
Doesn't that make this in the public API?
Can we keep this private?
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.
Never mind… Sorry about the noise.
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.
You bet I would be extra careful not to add any public API :-)
intervaly
was a public property; it is still a public property when set like this. (And in both cases the function that implements the getter is available as Bbox.invervaly.fget
; it used to also be available as Bbox._get_intervaly
but this PR removes that.)
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.
Thanks @anntzer for the clean up.
This looks good to me.
lib/matplotlib/transforms.py
Outdated
return ((x0 < x1 | ||
and (x >= x0 and x <= x1)) | ||
or (x >= x1 and x <= x0)) | ||
return self.xmin <= x <= self.xmax |
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.
That's so much better…
lib/matplotlib/transforms.py
Outdated
@@ -955,62 +897,62 @@ def update_from_data_xy(self, xy, ignore=None, updatex=True, updatey=True): | |||
def _set_x0(self, val): | |||
self._points[0, 0] = val | |||
self.invalidate() | |||
x0 = property(BboxBase._get_x0, _set_x0) | |||
x0 = property(BboxBase.x0.fget, _set_x0) |
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.
You could write this in the decorator form as:
@BboxBase.x0.setter
def x0(self, val):
self._points[0, 0] = val
self.invalidate()
or at least, it seems to work for me...
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, because this would update the setter of BboxBase
too. I could write
@partial(property, BboxBase.x0.fget)
def x0(self, val): ...
but that hardly seems like an improvement in readability...
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, never mind then...
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.
It does not rewrite the setter of base class for me (on both 2.7 and 3.5).
class A(object):
def __init__(self):
self._z = None
@property
def z(self):
return self._z
@z.setter
def z(self, val):
print('A')
self._z = val
class B(A):
@A.z.setter
def z(self, val):
print('B')
self._z = val
a = A()
a.z = 123
print(a.z)
b = B()
b.z = 456
print(b.z)
class C(object):
def __init__(self):
self._z = None
@property
def z(self):
return self._z
class D(A):
@C.z.setter
def z(self, val):
print('D')
self._z = val
try:
c = C()
c.z = 123
print('Error')
except AttributeError:
print('Pass')
d = D()
d.z = 456
print(d.z)
A
123
B
456
Pass
D
456
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.
Oh, indeed prop.setter
returns a new property, leaving the previous one untouched. The docs actually explicitly say that:
A property object has getter, setter, and deleter methods usable as decorators that create a copy of the property with the corresponding accessor function set to the decorated function.
(emphasis mine)
One new thing I learned today... and I'll update the PR accordngly.
@tacaswell I think your concerns about performance are very valid. For example
is three times slower. |
I tried adding a Now the question is, does this actually matter? It would be nice to have some actual performance tests (on "real" plots) to check that. |
@anntzer If I understand you correctly you are talking about fast onepass minmax algorithm. While it makes 25% less comparisons ( |
The point is that
will be faster than anything that goes through a function (/ property) call. |
2def7be
to
8a3bc52
Compare
Ah, so you worry about added function call/property access overhead? class Base(object):
intervalx = 123, 456
class A(Base):
def fully_containsx(self, x):
x0, x1 = self.intervalx
return ((x0 < x1
and (x > x0 and x < x1))
or (x > x1 and x < x0))
class B(Base):
@property
def xmin(self):
return min(self.intervalx)
@property
def xmax(self):
return max(self.intervalx)
def fully_containsx(self, x):
return self.xmin < x < self.xmax
|
That's basically the difference I measured, yes. |
2f8a568
to
1859a22
Compare
Current coverage is 61.86% (diff: 90.62%)@@ master #7394 diff @@
==========================================
Files 109 173 +64
Lines 46780 56092 +9312
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 31075 34702 +3627
- Misses 15705 21390 +5685
Partials 0 0
|
Any updates? Needs a rebase. |
Given the reasonable performance concerns I don't think this can go forward unless we somehow add performance regression tests and show that there won't be a significant slowdown. |
I wouldn't add a performance regression tests, but I think it is reasonable to benchmark the two options implementations. |
But this is so much down the stack that it could literally affect anything, so I don't think a microbenchmark really makes sense. I have no idea, for example, whether it would make plotting a lot of artists much slower because it may be doing a bunch of bbox comparisons... or perhaps not. |
Simplify the implementation of various methods. Note the switch to `np.min` rather than `min`, which avoids silently dropping `nan`s. Switch to modern property declarations. Cleanup some docstrings.
1859a22
to
c681756
Compare
@tacaswell I restored a fast(?) implementation of |
c681756
to
aa406bf
Compare
lib/matplotlib/transforms.py
Outdated
y1 = max(y1, np.max(ys)) | ||
|
||
return Bbox.from_extents(x0, y0, x1, y1) | ||
x0 = min(bbox.xmin for bbox in bboxes) |
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.
Can np.nan
or np.inf
come out of these?
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 guess I should use np.min
everywhere (which passes nan
through, whereas builtin min
drops them). (At least this would be consistent with bbox.xmin
.)
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.
Added a note to that effect.
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.
Looks good to me
Simplify the implementation of the various
contains
-like methods.Switch to modern property declarations. Cleanup some docstrings.