From ac555bc90352e6da424129a8a51f43545c81731c Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 02:15:55 +0200 Subject: [PATCH 01/21] Implement `AttributeError_str()` --- Objects/exceptions.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 149595e64cec144..0833791d5c707aa 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2702,6 +2702,23 @@ AttributeError_dealloc(PyObject *self) Py_TYPE(self)->tp_free(self); } +static PyObject * +AttributeError_str(PyObject *op) +{ + PyAttributeErrorObject *self = PyAttributeErrorObject_CAST(op); + if ( + self->obj + && (!PyTuple_GET_SIZE(self->args) + || (PyTuple_GET_SIZE(self->args) == 1 + && PyUnicode_Equal(PyTuple_GET_ITEM(self->args, 0), self->name)) + ) + ) { + return PyUnicode_FromFormat("%R has no attribute '%U'", + self->obj, self->name); + } + return BaseException_str(op); +} + static int AttributeError_traverse(PyObject *op, visitproc visit, void *arg) { @@ -2770,7 +2787,7 @@ static PyMethodDef AttributeError_methods[] = { ComplexExtendsException(PyExc_Exception, AttributeError, AttributeError, 0, AttributeError_methods, AttributeError_members, - 0, BaseException_str, 0, "Attribute not found."); + 0, AttributeError_str, 0, "Attribute not found."); /* * SyntaxError extends Exception From 4f22a162a15bc7cab7892b2dd96d8eb611128e08 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 02:34:00 +0200 Subject: [PATCH 02/21] Add tests --- Lib/test/test_exceptions.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index cc7faef93e1af7c..36a6d718f06ece0 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -10,6 +10,7 @@ from codecs import BOM_UTF8 from itertools import product from textwrap import dedent +from types import ModuleType from test.support import (captured_stderr, check_impl_detail, cpython_only, gc_collect, @@ -2047,6 +2048,38 @@ def blech(self): self.assertEqual("bluch", exc.name) self.assertEqual(obj, exc.obj) + def _test_generated_message(self, obj, name): + with self.assertRaises(AttributeError) as cm: + getattr(obj, name) + self.assertEqual(str(cm.exception), + f"{obj!r} has no attribute {name!r}") + + def test_getattr_error_message(self): + class RaiseWithName: + def __getattr__(self, name): + raise AttributeError(name) + + class BareRaise: + def __getattr__(self, name): + raise AttributeError + + self._test_generated_message(RaiseWithName(), "missing1") + self._test_generated_message(BareRaise(), "missing2") + + def test_module_getattr_attribute_error_message(self): + mod1 = ModuleType("raisewithname") + def raise_with_name(name): + raise AttributeError(name) + mod1.__getattr__ = raise_with_name + + mod2 = ModuleType("bareraise") + def bare_raise(name): + raise AttributeError + mod2.__getattr__ = bare_raise + + self._test_generated_message(mod1, "missing3") + self._test_generated_message(mod2, "missing4") + # Note: name suggestion tests live in `test_traceback`. From b30d6768cb96bc35f4d8e4d1983e91fec51c09b1 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 02:41:04 +0200 Subject: [PATCH 03/21] Check name too --- Objects/exceptions.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 0833791d5c707aa..6fa2c2f8f149ec0 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2708,6 +2708,7 @@ AttributeError_str(PyObject *op) PyAttributeErrorObject *self = PyAttributeErrorObject_CAST(op); if ( self->obj + && self->name && (!PyTuple_GET_SIZE(self->args) || (PyTuple_GET_SIZE(self->args) == 1 && PyUnicode_Equal(PyTuple_GET_ITEM(self->args, 0), self->name)) From 28e04d3e4213189e44ba539df01aab57ccdbb1fb Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 02:48:03 +0200 Subject: [PATCH 04/21] Add docs --- Doc/library/exceptions.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 3775d5ac81a2736..59ba5ca49b491f0 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -215,9 +215,18 @@ The following exceptions are the exceptions that are usually raised. The object that was accessed for the named attribute. + When possible, :attr:`name` and :attr:`obj` are set automatically + for failed attribute lookups. + .. versionchanged:: 3.10 Added the :attr:`name` and :attr:`obj` attributes. + .. versionchanged:: next + The default error message is now generated from :attr:`name` and + :attr:`obj` when both attributes are set and the exception was + constructed with no positional arguments, or with a single positional + argument equal to :attr:`name`. + .. exception:: EOFError Raised when the :func:`input` function hits an end-of-file condition (EOF) From e5c49220c73dcf84efed95ee15fded200e9c1e97 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 02:53:16 +0200 Subject: [PATCH 05/21] Add news entry --- .../2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst new file mode 100644 index 000000000000000..e3bcea972ca4cbd --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst @@ -0,0 +1,4 @@ +The default error message is now generated from ``name`` and +``obj`` attributes of :exc:`AttributeError` when both are set and +the exception was constructed with no positional arguments, or with +a single positional argument equal to :attr:`name`. Patch by Bartosz Sławecki. From 38545635df41c999dd9d0ef64ab33ca074a0f2ff Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 02:55:13 +0200 Subject: [PATCH 06/21] Improve formatting --- Objects/exceptions.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 6fa2c2f8f149ec0..d3f4eba3ed9a442 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2710,8 +2710,8 @@ AttributeError_str(PyObject *op) self->obj && self->name && (!PyTuple_GET_SIZE(self->args) - || (PyTuple_GET_SIZE(self->args) == 1 - && PyUnicode_Equal(PyTuple_GET_ITEM(self->args, 0), self->name)) + || (PyTuple_GET_SIZE(self->args) == 1 + && PyUnicode_Equal(PyTuple_GET_ITEM(self->args, 0), self->name)) ) ) { return PyUnicode_FromFormat("%R has no attribute '%U'", From 1e88331004cdbb0aa90e9e01233adaa55787880d Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 03:01:05 +0200 Subject: [PATCH 07/21] Simplify? --- Objects/exceptions.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index d3f4eba3ed9a442..dd55642130c46fc 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2706,18 +2706,14 @@ static PyObject * AttributeError_str(PyObject *op) { PyAttributeErrorObject *self = PyAttributeErrorObject_CAST(op); - if ( - self->obj - && self->name - && (!PyTuple_GET_SIZE(self->args) - || (PyTuple_GET_SIZE(self->args) == 1 - && PyUnicode_Equal(PyTuple_GET_ITEM(self->args, 0), self->name)) - ) - ) { - return PyUnicode_FromFormat("%R has no attribute '%U'", - self->obj, self->name); + if (!self->obj || !self->name || PyTuple_GET_SIZE(self->args) > 1 + || (PyTuple_GET_SIZE(self->args) == 1 && + !PyUnicode_Equal(PyTuple_GET_ITEM(self->args, 0), self->name))) + { + return BaseException_str(op); } - return BaseException_str(op); + return PyUnicode_FromFormat("%R has no attribute '%U'", + self->obj, self->name); } static int From 2fd216a0255da824db4c4e91d2f8cb562bd29961 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 03:06:59 +0200 Subject: [PATCH 08/21] Cooler formatting of the news entry --- .../2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst index e3bcea972ca4cbd..278bc2ac64abeba 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst @@ -1,4 +1,4 @@ -The default error message is now generated from ``name`` and -``obj`` attributes of :exc:`AttributeError` when both are set and -the exception was constructed with no positional arguments, or with -a single positional argument equal to :attr:`name`. Patch by Bartosz Sławecki. +The default error message is now generated from ``name`` and ``obj`` attributes +of :exc:`AttributeError` when both are set and the exception was constructed with +no positional arguments, or with a single positional argument equal to :attr:`name`. +Patch by Bartosz Sławecki. From 356f56eeac4fdd2dc5724541ad1e70fc0a711ee5 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 03:13:38 +0200 Subject: [PATCH 09/21] Test custom messages aren't overwritten --- Lib/test/test_exceptions.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 36a6d718f06ece0..7d08369549206e6 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -2063,8 +2063,13 @@ class BareRaise: def __getattr__(self, name): raise AttributeError + class RaiseCustom: + def __getattr__(self, name): + raise AttributeError("custom") + self._test_generated_message(RaiseWithName(), "missing1") self._test_generated_message(BareRaise(), "missing2") + self._test_generated_message(RaiseCustom(), "custom") def test_module_getattr_attribute_error_message(self): mod1 = ModuleType("raisewithname") @@ -2077,8 +2082,14 @@ def bare_raise(name): raise AttributeError mod2.__getattr__ = bare_raise + mod3 = ModuleType("bareraise") + def raise_custom(name): + raise AttributeError("custom") + mod3.__getattr__ = raise_custom + self._test_generated_message(mod1, "missing3") self._test_generated_message(mod2, "missing4") + self._test_generated_message(mod3, "custom") # Note: name suggestion tests live in `test_traceback`. From 8db03948be57346ea359b79dccbdb86165f02c2c Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 03:17:49 +0200 Subject: [PATCH 10/21] Fix news problem --- .../2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst index 278bc2ac64abeba..2852419c6d6e8a7 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-16-02-50-01.gh-issue-153785.fJqPKC.rst @@ -1,4 +1,4 @@ The default error message is now generated from ``name`` and ``obj`` attributes of :exc:`AttributeError` when both are set and the exception was constructed with -no positional arguments, or with a single positional argument equal to :attr:`name`. +no positional arguments, or with a single positional argument equal to ``name``. Patch by Bartosz Sławecki. From ebcc7958b59acdbb751a9c777d7b071aa7e756f1 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 03:20:19 +0200 Subject: [PATCH 11/21] Shorter test name --- Lib/test/test_exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 7d08369549206e6..5c04a02adf29f59 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -2071,7 +2071,7 @@ def __getattr__(self, name): self._test_generated_message(BareRaise(), "missing2") self._test_generated_message(RaiseCustom(), "custom") - def test_module_getattr_attribute_error_message(self): + def test_module_getattr_error_message(self): mod1 = ModuleType("raisewithname") def raise_with_name(name): raise AttributeError(name) From b6fda10e9d3b1084b5f3ba6b2f5140448c6644f8 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 03:21:18 +0200 Subject: [PATCH 12/21] Fix inconsistent style --- Objects/exceptions.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index dd55642130c46fc..50056635d0afdbe 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2707,8 +2707,8 @@ AttributeError_str(PyObject *op) { PyAttributeErrorObject *self = PyAttributeErrorObject_CAST(op); if (!self->obj || !self->name || PyTuple_GET_SIZE(self->args) > 1 - || (PyTuple_GET_SIZE(self->args) == 1 && - !PyUnicode_Equal(PyTuple_GET_ITEM(self->args, 0), self->name))) + || (PyTuple_GET_SIZE(self->args) == 1 + && !PyUnicode_Equal(PyTuple_GET_ITEM(self->args, 0), self->name))) { return BaseException_str(op); } From d624835c7f72a6fcd1a61a43bec957d2c6eff51d Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 04:20:38 +0200 Subject: [PATCH 13/21] Make thread-safe, presumably --- Objects/exceptions.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 50056635d0afdbe..aff5c19c578b52c 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2706,14 +2706,24 @@ static PyObject * AttributeError_str(PyObject *op) { PyAttributeErrorObject *self = PyAttributeErrorObject_CAST(op); - if (!self->obj || !self->name || PyTuple_GET_SIZE(self->args) > 1 + PyObject *ret, *arg; + Py_BEGIN_CRITICAL_SECTION(self); + if ( + !self->obj || !self->name || !PyUnicode_Check(self->name) + || PyTuple_GET_SIZE(self->args) > 1 || (PyTuple_GET_SIZE(self->args) == 1 - && !PyUnicode_Equal(PyTuple_GET_ITEM(self->args, 0), self->name))) - { - return BaseException_str(op); + && PyUnicode_Check(arg = PyTuple_GET_ITEM(self->args, 0)) + && !PyUnicode_Equal(arg, self->name)) + ) { + ret = BaseException_str(op); + } + else { + ret = PyUnicode_FromFormat("%R has no attribute '%U'", + self->obj, self->name); } - return PyUnicode_FromFormat("%R has no attribute '%U'", - self->obj, self->name); + Py_END_CRITICAL_SECTION(); + return ret; + } static int From c50a356e4e3cff2363cb130d6eec20dd68aee6c4 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 04:23:31 +0200 Subject: [PATCH 14/21] Rarest case goes last --- Objects/exceptions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index aff5c19c578b52c..a339d848a8a2ade 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2710,10 +2710,10 @@ AttributeError_str(PyObject *op) Py_BEGIN_CRITICAL_SECTION(self); if ( !self->obj || !self->name || !PyUnicode_Check(self->name) - || PyTuple_GET_SIZE(self->args) > 1 || (PyTuple_GET_SIZE(self->args) == 1 && PyUnicode_Check(arg = PyTuple_GET_ITEM(self->args, 0)) && !PyUnicode_Equal(arg, self->name)) + || PyTuple_GET_SIZE(self->args) != 0 ) { ret = BaseException_str(op); } From 7ad5204f0e29ca83fdc87516c693bd6c3f87e926 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 04:24:10 +0200 Subject: [PATCH 15/21] } else { --- Objects/exceptions.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index a339d848a8a2ade..6bab5e706db8205 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2716,8 +2716,7 @@ AttributeError_str(PyObject *op) || PyTuple_GET_SIZE(self->args) != 0 ) { ret = BaseException_str(op); - } - else { + } else { ret = PyUnicode_FromFormat("%R has no attribute '%U'", self->obj, self->name); } From b65a7fddb074fa40274cbffae728cf1189d13436 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 04:26:44 +0200 Subject: [PATCH 16/21] Remove unnecessary whitespace --- Objects/exceptions.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 6bab5e706db8205..ad1cc9a77ca21a2 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2722,7 +2722,6 @@ AttributeError_str(PyObject *op) } Py_END_CRITICAL_SECTION(); return ret; - } static int From e99d87553694bafe9ea623e5db3e8fcdc2e483ff Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 04:27:02 +0200 Subject: [PATCH 17/21] Fix style --- Objects/exceptions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index ad1cc9a77ca21a2..64f6a8cc948bae9 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2718,7 +2718,7 @@ AttributeError_str(PyObject *op) ret = BaseException_str(op); } else { ret = PyUnicode_FromFormat("%R has no attribute '%U'", - self->obj, self->name); + self->obj, self->name); } Py_END_CRITICAL_SECTION(); return ret; From 8c29de3460602e9bd4260df966f4faa174d6a1b7 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 04:37:36 +0200 Subject: [PATCH 18/21] Avoid nesting critical sections! --- Objects/exceptions.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 64f6a8cc948bae9..786f57bbc6c0b37 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2706,21 +2706,22 @@ static PyObject * AttributeError_str(PyObject *op) { PyAttributeErrorObject *self = PyAttributeErrorObject_CAST(op); - PyObject *ret, *arg; + PyObject *ret = NULL, *arg; Py_BEGIN_CRITICAL_SECTION(self); if ( - !self->obj || !self->name || !PyUnicode_Check(self->name) - || (PyTuple_GET_SIZE(self->args) == 1 - && PyUnicode_Check(arg = PyTuple_GET_ITEM(self->args, 0)) - && !PyUnicode_Equal(arg, self->name)) - || PyTuple_GET_SIZE(self->args) != 0 + self->obj && self->name && PyUnicode_Check(self->name) + && ((PyTuple_GET_SIZE(self->args) == 1 + && PyUnicode_Check(arg = PyTuple_GET_ITEM(self->args, 0)) + && PyUnicode_Equal(arg, self->name)) + || PyTuple_GET_SIZE(self->args) == 0) ) { - ret = BaseException_str(op); - } else { ret = PyUnicode_FromFormat("%R has no attribute '%U'", self->obj, self->name); } Py_END_CRITICAL_SECTION(); + if (!ret) { + ret = BaseException_str(op); + } return ret; } From 6faf358eb22dd52a3fcb054921b1ad34bd952bc4 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 05:48:55 +0200 Subject: [PATCH 19/21] Fix error handling, crete strong refs for interpolation --- Objects/exceptions.c | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 786f57bbc6c0b37..3760b17facd0d44 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2706,23 +2706,29 @@ static PyObject * AttributeError_str(PyObject *op) { PyAttributeErrorObject *self = PyAttributeErrorObject_CAST(op); - PyObject *ret = NULL, *arg; + PyObject *arg, *obj = NULL, *name; + Py_BEGIN_CRITICAL_SECTION(self); - if ( - self->obj && self->name && PyUnicode_Check(self->name) + if (self->obj && self->name && PyUnicode_Check(self->name) && ((PyTuple_GET_SIZE(self->args) == 1 && PyUnicode_Check(arg = PyTuple_GET_ITEM(self->args, 0)) && PyUnicode_Equal(arg, self->name)) - || PyTuple_GET_SIZE(self->args) == 0) - ) { - ret = PyUnicode_FromFormat("%R has no attribute '%U'", - self->obj, self->name); + || PyTuple_GET_SIZE(self->args) == 0)) + { + obj = Py_NewRef(self->obj); + name = Py_NewRef(self->name); } Py_END_CRITICAL_SECTION(); - if (!ret) { - ret = BaseException_str(op); + + if (!obj) { + return BaseException_str(op); } - return ret; + + PyObject *result = PyUnicode_FromFormat("%R has no attribute '%U'", + obj, name); + Py_DECREF(obj); + Py_DECREF(name); + return result; } static int From 086a0c947bf4cde5942691015bd8bbe54f856c08 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Thu, 16 Jul 2026 05:52:11 +0200 Subject: [PATCH 20/21] Multiline if properly done --- Objects/exceptions.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 3760b17facd0d44..c460f109c5d2881 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2709,12 +2709,13 @@ AttributeError_str(PyObject *op) PyObject *arg, *obj = NULL, *name; Py_BEGIN_CRITICAL_SECTION(self); - if (self->obj && self->name && PyUnicode_Check(self->name) + if ( + self->obj && self->name && PyUnicode_Check(self->name) && ((PyTuple_GET_SIZE(self->args) == 1 && PyUnicode_Check(arg = PyTuple_GET_ITEM(self->args, 0)) && PyUnicode_Equal(arg, self->name)) - || PyTuple_GET_SIZE(self->args) == 0)) - { + || PyTuple_GET_SIZE(self->args) == 0) + ) { obj = Py_NewRef(self->obj); name = Py_NewRef(self->name); } From e5bb17e5b1555b955d01568f500f9b447b6bdcd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Fri, 17 Jul 2026 01:25:53 +0200 Subject: [PATCH 21/21] Fix small text mistake in tests --- Lib/test/test_exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 5c04a02adf29f59..94b69a9a494551a 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -2082,7 +2082,7 @@ def bare_raise(name): raise AttributeError mod2.__getattr__ = bare_raise - mod3 = ModuleType("bareraise") + mod3 = ModuleType("custom") def raise_custom(name): raise AttributeError("custom") mod3.__getattr__ = raise_custom