From 5aa890844f75d45ad31c171b6949e49eb69ac77a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 19 Feb 2025 18:20:13 +0200 Subject: [PATCH] gh-127750: Restore inspect and pydoc support of singledispatchmethod 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 92be41dcf8e9e3..70c59b109d9703 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -1075,6 +1075,10 @@ def __getattr__(self, name): 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 facad478103668..24be9f01a82dbc 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 abd330c0de57ac..4ebe1c4615b7e3 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -3312,6 +3312,58 @@ def t(self, arg): 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 8e47df21cfef2e..09eb045dfe2c46 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -415,6 +415,27 @@ def test_isroutine(self): # 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))