gh-85294: Handle missing arguments to @singledispatchmethod gracefully by ammaraskar · Pull Request #21471 · python/cpython
I measure a small, but significant, speedup for the following patch (relative to your PR branch) when @singledispatchmethod is used on slotted classes. Note that funcname is only calculated in the except IndexError block:
--- 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:
- With this PR currently:
bench singledispatchmethod: Mean +- std dev: 824 ns +- 8 ns - With my proposed patch added to this PR:
bench singledispatchmethod: Mean +- std dev: 810 ns +- 20 ns
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 @singledispatchmethod, and that those should take priority.