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
API: check locator and formatter args when passed #10772
Conversation
@jklymak For what reason did you labelled it with the “API consistency” tag? |
Umm, because there is no argument-checking flag. |
There! |
Well it was more by curiosity that I was asking ^^. The docstrings seemed to be pretty clear about the kind of expected inputs and your changes is simply enforcing that information, is it not ;)? |
Well, the documentation is specifying the API 😉 |
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.
LGTM to me :)! (Approval modulo the tests are passing of course)
One could argue that the exception messages might be made even more “precise”, in the sense for example that the “formatter argument must be an instance of matplotlib.ticker.Formatter or of another derived class”. But IMO, even in its current state, this PR is a definitively “good enough©” improvement compared to the former situation...
I don't really like the change, in general we are (and Python is) pretty lax in accepting anything that duck types correctly, and just let things blow up if the argument has the wrong type. Regarding the error message (if this goes in): an instance of a subclass of Ticker is also an instance of Ticker (literally, as returned by |
Well, I'm certainly willing to go by convention, but the un-patched error is in
Worse, if you happen to drag your mouse across the figure window, the same error is emitted repeatedly in each draw attempt. |
If it looks like this will get approved, I'll make some tests so codecov is happy. But I'm not advocating strongly that it should be merged if its really atypical to have this type of argument checking... |
(I'm not trying to block the change, but I think it's worth getting some more opinions...) |
@jklymak I'm +1 on this if it has tests and with @afvincent suggestion of using 'is instance of" type language because I want this stuff to be more common in the codebase. |
Added tests.... happy to be tutored on making these parameterized... |
The cost of putting in these checks is low; they are simple and fast. I'm not in favor of checking all inputs at every stage, but this looks like a case where the cost/benefit ratio is favorable, so I lean towards accepting this PR. |
@@ -1568,6 +1568,9 @@ def set_major_formatter(self, formatter): | |||
|
|||
ACCEPTS: A :class:`~matplotlib.ticker.Formatter` instance | |||
""" | |||
if not isinstance(formatter, mticker.Formatter): |
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.
To support duck typing, Python code tries to avoid isinstance(obj, cls)
in favor of hasattr(obj, name)
. Not sure about the canonical attributes or methods of locators and formatters, however, I would suggest checks like if not hasattr(formatter, 'format_data')
and if not hasattr(locator, 'tick_values')
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.
Does that become an AttributeError in that case?
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.
eh, and probably should have been TypeError if not and AttributeError. Certainly ValueError is incorrect?
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'd go for TypeError. By intent, the hasattr
here is a kind of a loose type checking.
Parametrizing would be something like:
or
However, I think this is not readable and I'd leave the four single tests. |
64a65fc
to
769cfb8
Compare
lib/matplotlib/axis.py
Outdated
@@ -1568,6 +1568,9 @@ def set_major_formatter(self, formatter): | |||
|
|||
ACCEPTS: A :class:`~matplotlib.ticker.Formatter` instance | |||
""" | |||
if not hasattr(formatter, 'format_data'): | |||
raise TypeError("formatter argument should be instance of " |
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.
one could shorten this to
raise TypeError("formatter must be a matplotlib.ticker.Formatter")
But that's a matter of taste. I'll leave this up to you.
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.
Thats what I had before but folks wanted "instance of" 😉
I'm not asking for any more changes--it's good enough as-is--but I disagree with the switch to duck-typing here. Duck-typing is a useful technique, but it is not always more appropriate than checking for membership in a class. In the particular case here, there is no fundamental reason a Formatter needs a (It looks like we don't use the 'format_data' method anywhere except in the jpl_units test...) |
@efiring That actually makes sense to me. It'd be pretty strange situation where someone bothered to pass a formatter or locator in that was not a subclass... Further, its certainly bad to test on a method thats not used ( |
While I'm with @timhoffm that I don't think it actually needs to be parameterized, pushing his example more: @pytest.mark.parametrize('setter', [ax.xaxis.set_minor_formatter, ax.xaxis.set_major_formatter])
@pytest.mark.parametrize('cls', [matplotlib.ticker.LogFormatter, matplotlib.ticker.LogLocator])
def test_set_formatter_or_locator_type(setter, cls):
fig, ax = plt.subplots()
with pytest.raises(ValueError):
setter(cls()) and agree with @efiring on |
Should this be backported? |
The intention is to restrict backports to genuine bug-fixes, and soon only to critical bug-fixes, so this is not a candidate for backporting. |
PR Summary
Right now,
ax.set_major_formatter
andax.set_major_locator
throw pretty deep error messages if the wrong type of data is passed to them. This catches at entry.Closes #10769
Now returns:
instead of something down in the bowels of drawing the ticker...
PR Checklist