-
-
Notifications
You must be signed in to change notification settings - Fork 8k
check ret == False
in Timer._on_timer
#1493
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
I don't know how to write a timer test for matplotlib, but it should simply confirm that a callback that returns various values don't get unregistered after one call |
@@ -1123,7 +1123,7 @@ def _on_timer(self): | |||
''' | |||
for func, args, kwargs in self.callbacks: | |||
ret = func(*args, **kwargs) | |||
if not ret: | |||
if ret is False: |
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.
We might want to add a comment here stating that it has to be this form and why. This will prevent future devs from "fixing" this odd semantic.
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 would think the docstring three lines above is good enough, but perhaps it is not.
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.
Considering that the change that broke everything came about when we did the PEP8 changes, the docstrings wouldn't have been visible in the github diffs and so we didn't notice it then. Better safe than sorry.
Of course, I would have to wonder about the logic of depending on the return value of an arbitrary callback function to determine if it is to be removed or not...
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.
comment added
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.
This change is not clear at all, and should be commented.
I'm guessing you want to avoid the case were ret
is an empty list, or an integer equal to 0 etc... ie, all the case were ret
isn't a boolean, but evaluates to false.
That method is tricky and smelly: I wouldn't expect that in a python code.
Given the severity of #1492, It would probably make sense to make sure this gets onto the |
@@ -1123,7 +1123,9 @@ def _on_timer(self): | |||
''' | |||
for func, args, kwargs in self.callbacks: | |||
ret = func(*args, **kwargs) | |||
if not ret: | |||
# docstring above explains why we use `if ret is False` here, | |||
# instead of `if not ret`. |
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.
The comment doesn't really help, nor does the docstring. This code is not very pythonic (I would guess this was written by someone who comes from , and I think a good explanation is necessary in case of more code "cleanup".
Also, this code will break if the function called returns the integer 0
(if that is ever the case..)
My 2 cents,
N
From the docstring:
What would be more clear? Perhaps:
or
or, for
You mean it will behave as described in the docstring? False is described as a special case, so I would not expect 0 to trigger that event. If 0 should trigger it, the docstring should be updated to indicate that. Differentiating between False and 0 is uncommon, so perhaps not the best API. I was only trying to fix the code so it behaved as the API is described. Here's a question: Does anyone actually use this |
My mistake: what I meant is that the current "fix" does not behave as the old code was (before my change that breaks everything). A boolean in CPython inherits from a integer, so the difference is very little:
Also, this relies on a CPython implementation detail (False is a singleton): this may break in future version of CPython (but is highly unlikely) or another python implementation (I don't know anything about those). Yes: if greeting: I think indeed, raising an Exception is a better way to do that, but I think it is out of the scope of this PR. Maybe we can open a ticket (and a discussion on the mailing list). |
per review
I've changed the code to A future Issue can address how to do it better with an Exception, deprecation plan, etc. |
Is everybody happy with this? |
I would personally like @dopplershift opinion because I think he wrote the original logic. |
I should note that the current logic is now exactly the same as it was prior to the pep8 pass that caused the problem. The docstring now reflects that. |
Fix coercion of values to False in _on_timer() introduced in PEP8 clean-up. Fixes #1492.
Yup, I'm the one to thank/blame/set fire to for the current architecture of the timers (I am both glad and surprised to see their use outside of animations). The return False as a way for the callback to end itself started because that's how [Py]Gtk decides whether a timer should repeat--using the return of the callback (and really, that's inherited from the C toolkit). As the design evolved and was generalized across backends, this use of return True/False was no longer essential, but I never revisited that mechanism--it made perfect sense at the time from my gtk-warped point of view. (Currently, the only thing that must return True/False is the _on_timer() method of the GtkTimer.) I agree that an exception would make more sense from a pure Python standpoint, but at this point we have a published API that we don't want to break (at least IPython is using it apparently) and I don't think the "wart" of the return True/False is remotely big enough to make me want to change it. (I mean, it did work perfectly well until the PEP 8 "fix" ...) The fix looks good, so I'm merging. Thanks @minrk (and thanks @WeatherGod for bringing this to my attention). |
I have to disagree on many points: First, not only this is a unpythonic code: it is code that breaks very easily due to the nature of python. Also, it did not work before: it just didn't fail, and @minrk updated the docstring to specify the case of a null integer which was not mentioned before (and I'm not sure you spotted the fact that returning a 0 would behave like returning False :) ). I don't think because a API is public (is it really public ?), it should never change. Sure, it is best not to change the APIs all the time, but when there is a problem with one, better fix it sooner than later. Also, what @minrk mentions isn't that ipython uses the API: it's just a bug that is underlined when using interactive tools such as ipython. Hence, I think we should make the change (I can do the patch). You may think I am nitpicking here, but the few talks I had with very experience python programmers (CPython core dev in fact) convinced me we should not leave the code as it is right now. I don't know the code very well, but it doesn't seem like a big change to implement (whatever the option we choose to implement), and I think it would be worth it to quickly do a patch to measure how much lines of code it would involve. |
The API is public in terms of the fact that the documented behavior (that a user of the timer classes would rely upon) was that a callback returning False (or 0) causes the callback to cease to be called. IPython is using the API (though not this behavior), as they use our TimerMac class--which is why they reported the bug (matplotlib itself only uses the timers for animations). The PEP8 change was the only thing broken, as it made the code suddenly consider a return value of None (which happens in the cases where there is no return) the same as False--the original code did not intend this (and is why it wasn't written that way in the first place). I'm really fuzzy on how this will break "easily" in the future given that the requirement of == False is now better documented. This broke because semantically different code was committed as part of formatting changes and I've found it perfectly acceptable, and even needed, that False and None are handled completely differently. We could make it slightly more pythonic by the following (slight API change): if not ret and ret is not None: However, I do agree that the exception is the superior solution--I just think the case against the current code is overblown. If somebody cares enough to change this to an exception, then have at it. The steps would be:
(I believe that's been our policy previously for changes to API.) I lack the time and motivation to do this, but pledge to review a PR that does. |
I can do a PR when I have the time if everyone agrees that raising an exception is the way to go. |
The docstring states:
Cleanup commit 2f11dee changed this logic so any return value whose boolean interpretation is False (e.g. None) would be unregistered.
This restores the logic to check only for explicit False (not None, not 0, etc.).
fixes #1492