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