gh-127750: Restore inspect and pydoc support of singledispatchmethod (GH-130309)

The code is still flawed, because it does not recognize class and static
methods, and the first argument is not removed from the signature of
bound methods, but at least it does not worse than in 3.13 and older.
This commit is contained in:
Serhiy Storchaka 2025-02-20 11:08:49 +02:00 committed by GitHub
parent ed831b95a2
commit 10b32054ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 79 additions and 1 deletions

View file

@ -1075,6 +1075,10 @@ class _singledispatchmethod_get:
raise AttributeError
return getattr(self._unbound.func, name)
@property
def __wrapped__(self):
return self._unbound.func
@property
def register(self):
return self._unbound.register

View file

@ -447,7 +447,8 @@ def isroutine(object):
or isfunction(object)
or ismethod(object)
or ismethoddescriptor(object)
or ismethodwrapper(object))
or ismethodwrapper(object)
or isinstance(object, functools._singledispatchmethod_get))
def isabstract(object):
"""Return true if the object is an abstract base class (ABC)."""

View file

@ -3309,6 +3309,58 @@ class TestSingleDispatch(unittest.TestCase):
support.gc_collect()
self.assertIsNone(r())
def test_signatures(self):
@functools.singledispatch
def func(item, arg: int) -> str:
return str(item)
@func.register
def _(item: int, arg: bytes) -> str:
return str(item)
self.assertEqual(str(Signature.from_callable(func)),
'(item, arg: int) -> str')
def test_method_signatures(self):
class A:
def m(self, item, arg: int) -> str:
return str(item)
@classmethod
def cm(cls, item, arg: int) -> str:
return str(item)
@functools.singledispatchmethod
def func(self, item, arg: int) -> str:
return str(item)
@func.register
def _(self, item, arg: bytes) -> str:
return str(item)
@functools.singledispatchmethod
@classmethod
def cls_func(cls, item, arg: int) -> str:
return str(arg)
@func.register
@classmethod
def _(cls, item, arg: bytes) -> str:
return str(item)
@functools.singledispatchmethod
@staticmethod
def static_func(item, arg: int) -> str:
return str(arg)
@func.register
@staticmethod
def _(item, arg: bytes) -> str:
return str(item)
self.assertEqual(str(Signature.from_callable(A.func)),
'(self, item, arg: int) -> str')
self.assertEqual(str(Signature.from_callable(A().func)),
'(self, item, arg: int) -> str')
self.assertEqual(str(Signature.from_callable(A.cls_func)),
'(cls, item, arg: int) -> str')
self.assertEqual(str(Signature.from_callable(A.static_func)),
'(item, arg: int) -> str')
class CachedCostItem:
_cost = 1

View file

@ -415,6 +415,27 @@ class TestPredicates(IsTestBase):
# partial
self.assertTrue(inspect.isroutine(functools.partial(mod.spam)))
def test_isroutine_singledispatch(self):
self.assertTrue(inspect.isroutine(functools.singledispatch(mod.spam)))
class A:
@functools.singledispatchmethod
def method(self, arg):
pass
@functools.singledispatchmethod
@classmethod
def class_method(cls, arg):
pass
@functools.singledispatchmethod
@staticmethod
def static_method(arg):
pass
self.assertTrue(inspect.isroutine(A.method))
self.assertTrue(inspect.isroutine(A().method))
self.assertTrue(inspect.isroutine(A.static_method))
self.assertTrue(inspect.isroutine(A.class_method))
def test_isclass(self):
self.istest(inspect.isclass, 'mod.StupidGit')
self.assertTrue(inspect.isclass(list))