Skip to content

Commit ab8e8e9

Browse files
gh-140873: Add support of non-descriptor callables in functools.singledispatchmethod()
1 parent d440a0f commit ab8e8e9

File tree

5 files changed

+38
-3
lines changed

5 files changed

+38
-3
lines changed

Doc/library/functools.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ The :mod:`functools` module defines the following functions:
672672
dispatch>` :term:`generic function`.
673673

674674
To define a generic method, decorate it with the ``@singledispatchmethod``
675-
decorator. When defining a function using ``@singledispatchmethod``, note
675+
decorator. When defining a method using ``@singledispatchmethod``, note
676676
that the dispatch happens on the type of the first non-*self* or non-*cls*
677677
argument::
678678

@@ -716,6 +716,9 @@ The :mod:`functools` module defines the following functions:
716716

717717
.. versionadded:: 3.8
718718

719+
.. versionchanged:: next
720+
Added support of non-:term:`descriptor` callables.
721+
719722

720723
.. function:: update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
721724

Doc/whatsnew/3.15.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,14 @@ difflib
393393
(Contributed by Jiahao Li in :gh:`134580`.)
394394

395395

396+
functools
397+
---------
398+
399+
* :func:`~functools.singledispatchmethod` now supports non-:term:`descriptor`
400+
callables.
401+
(Contributed by Serhiy Storchaka in :gh:`140873`.)
402+
403+
396404
hashlib
397405
-------
398406

Lib/functools.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,10 @@ def __call__(self, /, *args, **kwargs):
10831083
'singledispatchmethod method')
10841084
raise TypeError(f'{funcname} requires at least '
10851085
'1 positional argument')
1086-
return self._dispatch(args[0].__class__).__get__(self._obj, self._cls)(*args, **kwargs)
1086+
method = self._dispatch(args[0].__class__)
1087+
if hasattr(method, "__get__"):
1088+
method = method.__get__(self._obj, self._cls)
1089+
return method(*args, **kwargs)
10871090

10881091
def __getattr__(self, name):
10891092
# Resolve these attributes lazily to speed up creation of

Lib/test/test_functools.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2785,7 +2785,7 @@ class Slot:
27852785
@functools.singledispatchmethod
27862786
@classmethod
27872787
def go(cls, item, arg):
2788-
pass
2788+
return item - arg
27892789

27902790
@go.register
27912791
@classmethod
@@ -2794,7 +2794,9 @@ def _(cls, item: int, arg):
27942794

27952795
s = Slot()
27962796
self.assertEqual(s.go(1, 1), 2)
2797+
self.assertEqual(s.go(1.5, 1), 0.5)
27972798
self.assertEqual(Slot.go(1, 1), 2)
2799+
self.assertEqual(Slot.go(1.5, 1), 0.5)
27982800

27992801
def test_staticmethod_slotted_class(self):
28002802
class A:
@@ -3485,6 +3487,23 @@ def _(item, arg: bytes) -> str:
34853487
self.assertEqual(str(Signature.from_callable(A.static_func)),
34863488
'(item, arg: int) -> str')
34873489

3490+
def test_method_non_descriptor(self):
3491+
class Callable:
3492+
def __init__(self, value):
3493+
self.value = value
3494+
def __call__(self, arg):
3495+
return self.value, arg
3496+
3497+
class A:
3498+
t = functools.singledispatchmethod(Callable('general'))
3499+
t.register(int, Callable('special'))
3500+
3501+
a = A()
3502+
self.assertEqual(a.t(0), ('special', 0))
3503+
self.assertEqual(a.t(2.5), ('general', 2.5))
3504+
self.assertEqual(A.t(0), ('special', 0))
3505+
self.assertEqual(A.t(2.5), ('general', 2.5))
3506+
34883507

34893508
class CachedCostItem:
34903509
_cost = 1
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add support of non-:term:`descriptor` callables in
2+
:func:`functools.singledispatchmethod`.

0 commit comments

Comments
 (0)