From 10b32054ad6bce821e3b40101d4414d025be6e36 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 20 Feb 2025 11:08:49 +0200 Subject: [PATCH] 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. --- Lib/functools.py | 4 +++ Lib/inspect.py | 3 +- Lib/test/test_functools.py | 52 +++++++++++++++++++++++++++ Lib/test/test_inspect/test_inspect.py | 21 +++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/Lib/functools.py b/Lib/functools.py index 92be41dcf8e9..70c59b109d97 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -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 diff --git a/Lib/inspect.py b/Lib/inspect.py index 124293727ca8..f143c89674b7 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -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).""" diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index a67c7b2280df..b272631ae72c 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -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 diff --git a/Lib/test/test_inspect/test_inspect.py b/Lib/test/test_inspect/test_inspect.py index 06785e275f6b..6a562108c3a3 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -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)) -- 2.47.3