gh-140873: Add support of non-descriptor callables in functools.singledispatchmethod() (GH-140884)

This commit is contained in:
Serhiy Storchaka
2025-11-13 19:48:52 +02:00
committed by GitHub
parent b99db92dde
commit b2b68d40f8
5 changed files with 52 additions and 3 deletions

View File

@@ -672,7 +672,7 @@ The :mod:`functools` module defines the following functions:
dispatch>` :term:`generic function`.
To define a generic method, decorate it with the ``@singledispatchmethod``
decorator. When defining a function using ``@singledispatchmethod``, note
decorator. When defining a method using ``@singledispatchmethod``, note
that the dispatch happens on the type of the first non-*self* or non-*cls*
argument::
@@ -716,6 +716,9 @@ The :mod:`functools` module defines the following functions:
.. versionadded:: 3.8
.. versionchanged:: next
Added support of non-:term:`descriptor` callables.
.. function:: update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)

View File

@@ -498,6 +498,14 @@ difflib
(Contributed by Jiahao Li in :gh:`134580`.)
functools
---------
* :func:`~functools.singledispatchmethod` now supports non-:term:`descriptor`
callables.
(Contributed by Serhiy Storchaka in :gh:`140873`.)
hashlib
-------

View File

@@ -1083,7 +1083,10 @@ class _singledispatchmethod_get:
'singledispatchmethod method')
raise TypeError(f'{funcname} requires at least '
'1 positional argument')
return self._dispatch(args[0].__class__).__get__(self._obj, self._cls)(*args, **kwargs)
method = self._dispatch(args[0].__class__)
if hasattr(method, "__get__"):
method = method.__get__(self._obj, self._cls)
return method(*args, **kwargs)
def __getattr__(self, name):
# Resolve these attributes lazily to speed up creation of

View File

@@ -2785,7 +2785,7 @@ class TestSingleDispatch(unittest.TestCase):
@functools.singledispatchmethod
@classmethod
def go(cls, item, arg):
pass
return item - arg
@go.register
@classmethod
@@ -2794,7 +2794,9 @@ class TestSingleDispatch(unittest.TestCase):
s = Slot()
self.assertEqual(s.go(1, 1), 2)
self.assertEqual(s.go(1.5, 1), 0.5)
self.assertEqual(Slot.go(1, 1), 2)
self.assertEqual(Slot.go(1.5, 1), 0.5)
def test_staticmethod_slotted_class(self):
class A:
@@ -3485,6 +3487,37 @@ class TestSingleDispatch(unittest.TestCase):
self.assertEqual(str(Signature.from_callable(A.static_func)),
'(item, arg: int) -> str')
def test_method_non_descriptor(self):
class Callable:
def __init__(self, value):
self.value = value
def __call__(self, arg):
return self.value, arg
class A:
t = functools.singledispatchmethod(Callable('general'))
t.register(int, Callable('special'))
@functools.singledispatchmethod
def u(self, arg):
return 'general', arg
u.register(int, Callable('special'))
v = functools.singledispatchmethod(Callable('general'))
@v.register(int)
def _(self, arg):
return 'special', arg
a = A()
self.assertEqual(a.t(0), ('special', 0))
self.assertEqual(a.t(2.5), ('general', 2.5))
self.assertEqual(A.t(0), ('special', 0))
self.assertEqual(A.t(2.5), ('general', 2.5))
self.assertEqual(a.u(0), ('special', 0))
self.assertEqual(a.u(2.5), ('general', 2.5))
self.assertEqual(a.v(0), ('special', 0))
self.assertEqual(a.v(2.5), ('general', 2.5))
class CachedCostItem:
_cost = 1

View File

@@ -0,0 +1,2 @@
Add support of non-:term:`descriptor` callables in
:func:`functools.singledispatchmethod`.