From 66eb6036b3c9f2d35a19fd0ab4641a8c8abd837f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 17 Feb 2025 11:13:59 +0200 Subject: [PATCH] gh-127750: Improve repr of functools.singledispatchmethod --- Lib/functools.py | 22 +++++++ Lib/test/test_functools.py | 61 +++++++++++++++++++ ...-02-17-12-36-39.gh-issue-127750.ZC-hBq.rst | 2 + 3 files changed, 85 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-02-17-12-36-39.gh-issue-127750.ZC-hBq.rst diff --git a/Lib/functools.py b/Lib/functools.py index 92be41dcf8e9e3..c112742ba80e23 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -1040,6 +1040,15 @@ def __get__(self, obj, cls=None): def __isabstractmethod__(self): return getattr(self.func, '__isabstractmethod__', False) + def __repr__(self): + try: + name = self.func.__qualname__ + except AttributeError: + try: + name = self.func.__name__ + except AttributeError: + name = '?' + return f'' class _singledispatchmethod_get: def __init__(self, unbound, obj, cls): @@ -1059,6 +1068,19 @@ def __init__(self, unbound, obj, cls): except AttributeError: pass + def __repr__(self): + try: + name = self.__qualname__ + except AttributeError: + try: + name = self.__name__ + except AttributeError: + name = '?' + if self._obj is not None: + return f'' + else: + return f'' + def __call__(self, /, *args, **kwargs): if not args: funcname = getattr(self._unbound.func, '__name__', diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index abd330c0de57ac..8939dd7a21e45c 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2936,6 +2936,67 @@ def static_func(arg: int) -> str: self.assertEqual(A.static_func.__name__, 'static_func') self.assertEqual(A().static_func.__name__, 'static_func') + def test_method_repr(self): + class Callable: + def __call__(self, *args): + pass + + class CallableWithName: + __name__ = 'NOQUALNAME' + def __call__(self, *args): + pass + + class A: + @functools.singledispatchmethod + def func(self, arg): + pass + @functools.singledispatchmethod + @classmethod + def cls_func(cls, arg): + pass + @functools.singledispatchmethod + @staticmethod + def static_func(arg): + pass + # No __qualname__, only __name__ + no_qualname = functools.singledispatchmethod(CallableWithName()) + # No __qualname__, no __name__ + no_name = functools.singledispatchmethod(Callable()) + + self.assertEqual(repr(A.__dict__['func']), + f'') + self.assertEqual(repr(A.__dict__['cls_func']), + f'') + self.assertEqual(repr(A.__dict__['static_func']), + f'') + self.assertEqual(repr(A.__dict__['no_qualname']), + f'') + self.assertEqual(repr(A.__dict__['no_name']), + f'') + + self.assertEqual(repr(A.func), + f'') + self.assertEqual(repr(A.cls_func), + f'') + self.assertEqual(repr(A.static_func), + f'') + self.assertEqual(repr(A.no_qualname), + f'') + self.assertEqual(repr(A.no_name), + f'') + + a = A() + self.assertEqual(repr(a.func), + f'') + self.assertEqual(repr(a.cls_func), + f'') + self.assertEqual(repr(a.static_func), + f'') + self.assertEqual(repr(a.no_qualname), + f'') + self.assertEqual(repr(a.no_name), + f'') + def test_double_wrapped_methods(self): def classmethod_friendly_decorator(func): wrapped = func.__func__ diff --git a/Misc/NEWS.d/next/Library/2025-02-17-12-36-39.gh-issue-127750.ZC-hBq.rst b/Misc/NEWS.d/next/Library/2025-02-17-12-36-39.gh-issue-127750.ZC-hBq.rst new file mode 100644 index 00000000000000..e438dbba52192c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-02-17-12-36-39.gh-issue-127750.ZC-hBq.rst @@ -0,0 +1,2 @@ +Improve repr of :class:`functools.singledispatchmethod` methods and +descriptors.