From c6c2d335462d730b7a3b485add74758966196737 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 20 Feb 2025 11:08:49 +0200 Subject: [PATCH] [3.13] gh-127750: Backport some tests for singledispatchmethod (GH-130309) (cherry picked from commit 395335d0ff292a15e26575e06f603304d9161ee1) (cherry picked from commit 10b32054ad6bce821e3b40101d4414d025be6e36) --- Lib/test/test_functools.py | 111 ++++++++++++++++++++++++++ Lib/test/test_inspect/test_inspect.py | 21 +++++ 2 files changed, 132 insertions(+) diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 438cc96f3a72d8..544107e5a1c1e3 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -2758,6 +2758,7 @@ def static_func(arg: int) -> str: """My function docstring""" return str(arg) + prefix = A.__qualname__ + '.' for meth in ( A.func, A().func, @@ -2767,6 +2768,7 @@ def static_func(arg: int) -> str: A().static_func ): with self.subTest(meth=meth): + self.assertEqual(meth.__qualname__, prefix + meth.__name__) self.assertEqual(meth.__doc__, ('My function docstring' if support.HAVE_DOCSTRINGS @@ -3077,6 +3079,115 @@ def _(arg: typing.List[float] | bytes): self.assertEqual(f(""), "default") self.assertEqual(f(b""), "default") + def test_method_equal_instances(self): + # gh-127750: Reference to self was cached + class A: + def __eq__(self, other): + return True + def __hash__(self): + return 1 + @functools.singledispatchmethod + def t(self, arg): + return self + + a = A() + b = A() + self.assertIs(a.t(1), a) + self.assertIs(b.t(2), b) + + def test_method_bad_hash(self): + class A: + def __eq__(self, other): + raise AssertionError + def __hash__(self): + raise AssertionError + @functools.singledispatchmethod + def t(self, arg): + pass + + # Should not raise + A().t(1) + hash(A().t) + A().t == A().t + + def test_method_no_reference_loops(self): + # gh-127750: Created a strong reference to self + class A: + @functools.singledispatchmethod + def t(self, arg): + return weakref.ref(self) + + a = A() + r = a.t(1) + self.assertIsNotNone(r()) + del a # delete a after a.t + if not support.check_impl_detail(cpython=True): + support.gc_collect() + self.assertIsNone(r()) + + a = A() + t = a.t + del a # delete a before a.t + support.gc_collect() + r = t(1) + self.assertIsNotNone(r()) + del t + if not support.check_impl_detail(cpython=True): + 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 f30dc7affda11a..a88f1713210e1b 100644 --- a/Lib/test/test_inspect/test_inspect.py +++ b/Lib/test/test_inspect/test_inspect.py @@ -408,6 +408,27 @@ def test_isroutine(self): # partial self.assertFalse(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))