diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index d5b00a3bb628cb..12dcec5dfda94a 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -4355,6 +4355,43 @@ class DelattrSuggestionTests(BaseSuggestionTests): attr_function = delattr +class SetattrSuggestionTests(SuggestionFormattingTestMixin): + def get_suggestion(self, obj, attr_name=None): + if attr_name is not None: + def callable(): + setattr(obj, attr_name, None) + else: + callable = obj + result_lines = self.get_exception(callable, slice_start=-1, slice_end=None) + return result_lines[0] + + def test_property_no_setter_suggests_similar(self): + class A: + @property + def computed_a(self): + return 1 + + @property + def computed_b(self): + return 2 + + @computed_b.setter + def computed_b(self, val): + pass + + actual = self.get_suggestion(A(), 'computed_a') + self.assertIn("Did you mean '.computed_b'", actual) + + def test_property_no_setter_no_false_suggestion(self): + class A: + @property + def computed_a(self): + return 1 + + actual = self.get_suggestion(A(), 'computed_a') + self.assertNotIn("Did you mean", actual) + + class SuggestionFormattingTestBase(SuggestionFormattingTestMixin): def test_attribute_error_with_failing_dict(self): class T: @@ -5178,6 +5215,30 @@ class CPythonDelattrSuggestionFormattingTests( internal traceback printing. """ + +class PurePythonSetattrSuggestionFormattingTests( + PurePythonExceptionFormattingMixin, + SetattrSuggestionTests, + unittest.TestCase, +): + """ + Tests for "Did you mean" suggestions when setting a read-only property, + using the pure Python traceback implementation. + """ + + +@cpython_only +class CPythonSetattrSuggestionFormattingTests( + CAPIExceptionFormattingMixin, + SetattrSuggestionTests, + unittest.TestCase, +): + """ + Same set of tests (for attribute setting) but with Python's internal + traceback printing. + """ + + class MiscTest(unittest.TestCase): def test_all(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-30-00-00-00.gh-issue-130399.property-setter-suggest.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-30-00-00-00.gh-issue-130399.property-setter-suggest.rst new file mode 100644 index 00000000000000..a3c5cfaa0aec7a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-30-00-00-00.gh-issue-130399.property-setter-suggest.rst @@ -0,0 +1,3 @@ +:exc:`AttributeError` raised when setting a read-only property now includes +"Did you mean" suggestions, matching the behaviour already present for +attribute lookup and deletion. diff --git a/Objects/object.c b/Objects/object.c index c7aeba0cee22c6..691903f7ea9e95 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2061,6 +2061,9 @@ _PyObject_GenericSetAttrWithDict(PyObject *obj, PyObject *name, f = Py_TYPE(descr)->tp_descr_set; if (f != NULL) { res = f(descr, obj, value); + if (res < 0) { + _PyObject_SetAttributeErrorContext(obj, name); + } goto done; } }