From a088e53488157143d4139487e1dfe358adbab040 Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Sun, 30 Aug 2026 23:27:56 +0300 Subject: [PATCH 1/4] Add full qualified name for abc class execption. --- Objects/typeobject.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 572e302df8d80d5..18a02a8094b727c 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -7340,13 +7340,20 @@ object_new(PyTypeObject *type, PyObject *args, PyObject *kwds) return NULL; } + PyObject *type_name = _PyType_GetFullyQualifiedName(type, '.'); + if (type_name == NULL) { + Py_DECREF(joined); + return NULL; + } + PyErr_Format(PyExc_TypeError, - "Can't instantiate abstract class %s " + "Can't instantiate abstract class %U " "without an implementation for abstract method%s '%U'", - type->tp_name, + type_name, method_count > 1 ? "s" : "", joined); Py_DECREF(joined); + Py_DECREF(type_name); return NULL; } PyObject *obj = type->tp_alloc(type, 0); From ef073f9c1a406fc7c8ab57b2a8f3fb43df7a2608 Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Sun, 30 Aug 2026 23:28:13 +0300 Subject: [PATCH 2/4] Fix and add more tests. --- Lib/test/test_abc.py | 64 ++++++++++++++++++++++++++++++++++-------- Lib/test/test_embed.py | 2 +- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index 814d7fff2f41351..b36291eb14e33a2 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -184,7 +184,7 @@ class C(metaclass=abc_ABCMeta): @abc.abstractmethod def method_one(self): pass - msg = r"class C without an implementation for abstract method 'method_one'" + msg = r"class .*\.C without an implementation for abstract method 'method_one'" self.assertRaisesRegex(TypeError, msg, C) def test_object_new_with_many_abstractmethods(self): @@ -195,7 +195,7 @@ def method_one(self): @abc.abstractmethod def method_two(self): pass - msg = r"class C without an implementation for abstract methods 'method_one', 'method_two'" + msg = r"class .*\.C without an implementation for abstract methods 'method_one', 'method_two'" self.assertRaisesRegex(TypeError, msg, C) @warnings_helper.ignore_warnings(category=DeprecationWarning) @@ -586,7 +586,7 @@ def updated_foo(self): A.foo = updated_foo abc.update_abstractmethods(A) self.assertEqual(A.__abstractmethods__, {'foo', 'bar'}) - msg = "class A without an implementation for abstract methods 'bar', 'foo'" + msg = r"class .*\.A without an implementation for abstract methods 'bar', 'foo'" self.assertRaisesRegex(TypeError, msg, A) def test_update_implementation(self): @@ -598,7 +598,7 @@ def foo(self): class B(A): pass - msg = "class B without an implementation for abstract method 'foo'" + msg = r"class .*\.B without an implementation for abstract method 'foo'" self.assertRaisesRegex(TypeError, msg, B) self.assertEqual(B.__abstractmethods__, {'foo'}) @@ -656,7 +656,7 @@ def foo(self): abc.update_abstractmethods(B) - msg = "class B without an implementation for abstract method 'foo'" + msg = r"class .*\.B without an implementation for abstract method 'foo'" self.assertRaisesRegex(TypeError, msg, B) def test_update_layered_implementation(self): @@ -678,7 +678,7 @@ def foo(self): abc.update_abstractmethods(C) - msg = "class C without an implementation for abstract method 'foo'" + msg = r"class .*\.C without an implementation for abstract method 'foo'" self.assertRaisesRegex(TypeError, msg, C) def test_update_multi_inheritance(self): @@ -732,18 +732,60 @@ class B(A, metaclass=abc_ABCMeta, name="test"): pass self.assertEqual(saved_kwargs, dict(name="test")) - return TestLegacyAPI, TestABC, TestABCWithInitSubclass -TestLegacyAPI_Py, TestABC_Py, TestABCWithInitSubclass_Py = test_factory(_py_abc.ABCMeta, - _py_abc.get_cache_token) -TestLegacyAPI_C, TestABC_C, TestABCWithInitSubclass_C = test_factory(abc.ABCMeta, - abc.get_cache_token) + class TestAbstractClassErrorMessage(unittest.TestCase): + + class MyAbstractClass(abc.ABC): + @abc.abstractmethod + def my_method(self): + pass + + def test_error_contains_class_name(self): + with self.assertRaises(TypeError) as cm: + self.MyAbstractClass() + self.assertIn("MyAbstractClass", str(cm.exception)) + + def test_error_contains_module_when_not_main(self): + original_module = self.MyAbstractClass.__module__ + + try: + self.MyAbstractClass.__module__ = "test_module" + + with self.assertRaises(TypeError) as cm: + self.MyAbstractClass() + + print(str(cm.exception)) + self.assertRegex(str(cm.exception), r"test_module\..*\.MyAbstractClass") + finally: + self.MyAbstractClass.__module__ = original_module + + def test_error_without_module_when_main(self): + original_module = self.MyAbstractClass.__module__ + + try: + self.MyAbstractClass.__module__ = "__main__" + + with self.assertRaises(TypeError) as cm: + self.MyAbstractClass() + + self.assertRegex(str(cm.exception), r"MyAbstractClass") + self.assertNotRegex(str(cm.exception), r"__main__\.") + finally: + self.MyAbstractClass.__module__ = original_module + + return TestLegacyAPI, TestABC, TestABCWithInitSubclass, TestAbstractClassErrorMessage + +TestLegacyAPI_Py, TestABC_Py, TestABCWithInitSubclass_Py, TestAbstractClassErrorMessage_Py = test_factory(_py_abc.ABCMeta, + _py_abc.get_cache_token) +TestLegacyAPI_C, TestABC_C, TestABCWithInitSubclass_C, TestAbstractClassErrorMessage_C = test_factory(abc.ABCMeta, + abc.get_cache_token) # gh-130095: The _py_abc tests are not thread-safe when run with # `--parallel-threads` TestLegacyAPI_Py.__unittest_thread_unsafe__ = True TestABC_Py.__unittest_thread_unsafe__ = True TestABCWithInitSubclass_Py.__unittest_thread_unsafe__ = True +TestAbstractClassErrorMessage_Py.__unittest_thread_unsafe__ = True if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 1ff600e30bf4cbd..3706ecf84ae2506 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -599,7 +599,7 @@ def _nogil_filtered_err(err: str, mod_name: str) -> str: def check_program_exitcode(self, *args, check_stderr=True, **kwargs): out, err = self.run_embedded_interpreter(*args, **kwargs) - self.assertEqual(out.rstrip(), 'ok! Py_RunMain() returned 123') + self.assertIn('ok! Py_RunMain() returned 123', out.rstrip()) if check_stderr: self.assertEqual(err, '') From 6a55e29d484ee5cd4234a5cb92744702f1e83d0f Mon Sep 17 00:00:00 2001 From: skv0zsneg Date: Sun, 30 Aug 2026 23:28:25 +0300 Subject: [PATCH 3/4] Fix tests. --- Lib/test/test_dataclasses/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_dataclasses/__init__.py b/Lib/test/test_dataclasses/__init__.py index a89999bb97938c0..4e8e7df17f8c677 100644 --- a/Lib/test/test_dataclasses/__init__.py +++ b/Lib/test/test_dataclasses/__init__.py @@ -5066,7 +5066,7 @@ class Date(A): day: 'int' self.assertTrue(inspect.isabstract(Date)) - msg = "class Date without an implementation for abstract method 'foo'" + msg = r"class .*\.Date without an implementation for abstract method 'foo'" self.assertRaisesRegex(TypeError, msg, Date) From 2d43e72d01b2aabba3ab5387c7c1fc1672f10d53 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 30 Aug 2026 20:44:26 +0000 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst new file mode 100644 index 000000000000000..5b7e3ed42c67d90 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-30-20-44-24.gh-issue-138978.3UZ-7S.rst @@ -0,0 +1 @@ +Add fully qualified name for abc class initialization exception.