-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
gh-85294: Handle missing arguments to @singledispatchmethod gracefully #21471
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
gh-85294: Handle missing arguments to @singledispatchmethod gracefully #21471
Conversation
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 am not the assigned reviewer, but was just looking through open PRs (so my review has little weight)
Your changes look good!! 👍
Just added one minor comment for changing the test method name.
Co-authored-by: nagarajan <68099336+nagarajan@users.noreply.github.com>
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!
Any word on this? I've adapted to recognizing the error pattern, but this fix would have saved me some time, and I imagine it's costing time for others. |
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 accidentally opened a duplicate PR for this since I ran into it today and it wasn't pleasant to figure out, +1.
It seems this PR has been forgotten. I ran into this recently and also took me some time to figure out. It would be nice to merge it. |
if not args: | ||
raise TypeError(f'{funcname} requires at least ' | ||
'1 positional argument') | ||
return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs) |
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 haven't done benchmarks, but is there a risk that this could add unnecessary overhead to the happy path? What if we did something like this @serhiy-storchaka?
if not args: | |
raise TypeError(f'{funcname} requires at least ' | |
'1 positional argument') | |
return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs) | |
try: | |
key = args[0].__class__ | |
except IndexError: | |
raise TypeError(f'{funcname} requires at least ' | |
'1 positional argument') from None | |
return dispatch(key).__get__(obj, cls)(*args, **kwargs) |
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 looks reasonable, but could you please benchmark this? Your code is not zero cost either, it adds STORE_FAST+LOAD_FAST instead of LOAD_FAST+TO_BOOL+POP_JUMP_IF_TRUE.
If there is a difference, we perhaps should apply the same optimization in singledispatch()
.
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.
Yeah, I'll benchmark it.
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.
For now, I reverted this change because it complicates the code. And the solution for other issues may change it anyway. It is better to defer it until the code will be stabilized.
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This reverts commit f41eafb.
I measure a small, but significant, speedup for the following patch (relative to your PR branch) when --- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -967,12 +967,14 @@ def __get__(self, obj, cls=None):
return _method
dispatch = self.dispatcher.dispatch
- funcname = getattr(self.func, '__name__', 'singledispatchmethod method')
def _method(*args, **kwargs):
- if not args:
+ try:
+ key = args[0].__class__
+ except IndexError:
+ funcname = getattr(self.func, '__name__', 'singledispatchmethod method')
raise TypeError(f'{funcname} requires at least '
- '1 positional argument')
- return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs)
+ '1 positional argument') from None
+ return dispatch(key).__get__(obj, cls)(*args, **kwargs) Benchmark script: import pyperf
runner = pyperf.Runner()
runner.timeit(name="bench singledispatchmethod",
stmt="""
_ = t.go(1, 1)
""",
setup="""
from functools import singledispatch, singledispatchmethod
class Test:
__slots__ = ()
@singledispatchmethod
def go(self, item, arg):
pass
@go.register
def _(self, item: int, arg):
return item + arg
t = Test()
""") I could not measure any performance difference from my original suggestion at #21471 (comment). Numbers from that benchmark:
I'm fine with it if you want to defer optimising this area of code for now; I agree there are lots of open bug reports regarding |
…cefully (pythonGH-21471) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
…cefully (pythonGH-21471) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
…cefully (pythonGH-21471) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This prevents a confusing error message when you fail to pass an argument. The code mirrors the check already present in
singledispatch
.Before
After
https://bugs.python.org/issue41122