Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Doc/library/functools.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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::

Expand Down Expand Up @@ -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)

Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,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
-------

Expand Down
5 changes: 4 additions & 1 deletion Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1083,7 +1083,10 @@ def __call__(self, /, *args, **kwargs):
'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
Expand Down
21 changes: 20 additions & 1 deletion Lib/test/test_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -2785,7 +2785,7 @@ class Slot:
@functools.singledispatchmethod
@classmethod
def go(cls, item, arg):
pass
return item - arg

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

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:
Expand Down Expand Up @@ -3485,6 +3487,23 @@ def _(item, arg: bytes) -> str:
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'))

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))


class CachedCostItem:
_cost = 1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support of non-:term:`descriptor` callables in
:func:`functools.singledispatchmethod`.
Loading