Skip to content

Commit b2b68d4

Browse files
pythongh-140873: Add support of non-descriptor callables in functools.singledispatchmethod() (pythonGH-140884)
1 parent b99db92 commit b2b68d4

File tree

5 files changed

+52
-3
lines changed

5 files changed

+52
-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
@@ -498,6 +498,14 @@ difflib
498498
(Contributed by Jiahao Li in :gh:`134580`.)
499499

500500

501+
functools
502+
---------
503+
504+
* :func:`~functools.singledispatchmethod` now supports non-:term:`descriptor`
505+
callables.
506+
(Contributed by Serhiy Storchaka in :gh:`140873`.)
507+
508+
501509
hashlib
502510
-------
503511

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: 34 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,37 @@ 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+
@functools.singledispatchmethod
3502+
def u(self, arg):
3503+
return 'general', arg
3504+
u.register(int, Callable('special'))
3505+
3506+
v = functools.singledispatchmethod(Callable('general'))
3507+
@v.register(int)
3508+
def _(self, arg):
3509+
return 'special', arg
3510+
3511+
a = A()
3512+
self.assertEqual(a.t(0), ('special', 0))
3513+
self.assertEqual(a.t(2.5), ('general', 2.5))
3514+
self.assertEqual(A.t(0), ('special', 0))
3515+
self.assertEqual(A.t(2.5), ('general', 2.5))
3516+
self.assertEqual(a.u(0), ('special', 0))
3517+
self.assertEqual(a.u(2.5), ('general', 2.5))
3518+
self.assertEqual(a.v(0), ('special', 0))
3519+
self.assertEqual(a.v(2.5), ('general', 2.5))
3520+
34883521

34893522
class CachedCostItem:
34903523
_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)