8000 gh-85160: improve performance of `functools.singledispatchmethod` (#1… · python/cpython@3e334ae · GitHub
[go: up one dir, main page]

Skip to content

Commit 3e334ae

Browse files
eendebakptmental32AlexWaygood
authored
gh-85160: improve performance of functools.singledispatchmethod (#107148)
Co-authored-by: mental <m3nta1@yahoo.com> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent 9641c4d commit 3e334ae

File tree

3 files changed

+90
-2
lines changed

3 files changed

+90
-2
lines changed

Lib/functools.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -928,11 +928,14 @@ class singledispatchmethod:
928928
"""
929929

930930
def __init__(self, func):
931+
import weakref # see comment in singledispatch function
931932
if not callable(func) and not hasattr(func, "__get__"):
932933
raise TypeError(f"{func!r} is not callable or a descriptor")
933934

934935
self.dispatcher = singledispatch(func)
935936
self.func = func
937+
self._method_cache = weakref.WeakKeyDictionary()
938+
self._all_weakrefable_instances = True
936939

937940
def register(self, cls, method=None):
938941
"""generic_method.register(cls, func) -> func
@@ -942,13 +945,27 @@ def register(self, cls, method=None):
942945
return self.dispatcher.register(cls, func=method)
943946

944947
def __get__(self, obj, cls=None):
948+
if self._all_weakrefable_instances:
949+
try:
950+
_method = self._method_cache[obj]
951+
except TypeError:
952+
self._all_weakrefable_instances = False
953+
except KeyError:
954+
pass
955+
else:
956+
return _method
957+
958+
dispatch = self.dispatcher.dispatch
945959
def _method(*args, **kwargs):
946-
method = self.dispatcher.dispatch(args[0].__class__)
947-
return method.__get__(obj, cls)(*args, **kwargs)
960+
return dispatch(args[0].__class__).__get__(obj, cls)(*args, **kwargs)
948961

949962
_method.__isabstractmethod__ = self.__isabstractmethod__
950963
_method.register = self.register
951964
update_wrapper(_method, self.func)
965+
966+
if self._all_weakrefable_instances:
967+
self._method_cache[obj] = _method
968+
952969
return _method
953970

954971
@property

Lib/test/test_functools.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2474,6 +2474,74 @@ def _(arg):
24742474
self.assertTrue(A.t(''))
24752475
self.assertEqual(A.t(0.0), 0.0)
24762476

2477+
def test_slotted_class(self):
2478+
class Slot:
2479+
__slots__ = ('a', 'b')
2480+
@functools.singledispatchmethod
2481+
def go(self, item, arg):
2482+
pass
2483+
2484+
@go.register
2485+
def _(self, item: int, arg):
2486+
return item + arg
2487+
2488+
s = Slot()
2489+
self.assertEqual(s.go(1, 1), 2)
2490+
2491+
def test_classmethod_slotted_class(self):
2492+
class Slot:
2493+
__slots__ = ('a', 'b')
2494+
@functools.singledispatchmethod
2495+
@classmethod
2496+
def go(cls, item, arg):
2497+
pass
2498+
2499+
@go.register
2500+
@classmethod
2501+
def _(cls, item: int, arg):
2502+
return item + arg
2503+
2504+
s = Slot()
2505+
self.assertEqual(s.go(1, 1), 2)
2506+
self.assertEqual(Slot.go(1, 1), 2)
2507+
2508+
def test_staticmethod_slotted_class(self):
2509+
class A:
2510+
__slots__ = ['a']
2511+
@functools.singledispatchmethod
2512+
@staticmethod
2513+
def t(arg):
2514+
return arg
2515+
@t.register(int)
2516+
@staticmethod
2517+
def _(arg):
2518+
return isinstance(arg, int)
2519+
@t.register(str)
2520+
@staticmethod
2521+
def _(arg):
2522+
return isinstance(arg, str)
2523+
a = A()
2524+
2525+
self.assertTrue(A.t(0))
2526+
self.assertTrue(A.t(''))
2527+
self.assertEqual(A.t(0.0), 0.0)
2528+
self.assertTrue(a.t(0))
2529+
self.assertTrue(a.t(''))
2530+
self.assertEqual(a.t(0.0), 0.0)
2531+
2532+
def test_assignment_behavior(self):
2533+
# see gh-106448
2534+
class A:
2535+
@functools.singledispatchmethod
2536+
def t(arg):
2537+
return arg
2538+
2539+
a = A()
2540+
a.t.foo = 'bar'
2541+
a2 = A()
2542+
with self.assertRaises(AttributeError):
2543+
a2.t.foo
2544+
24772545
def test_classmethod_register(self):
24782546
class A:
24792547
def __init__(self, arg):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Improve performance of :class:`functools.singledispatchmethod` by caching the
2+
generated dispatch wrapper. Optimization suggested by frederico. Patch by
3+
@mental32, Alex Waygood and Pieter Eendebak.

0 commit comments

Comments
 (0)
0