https://github.com/python/cpython/commit/2f4eb34bd23d57d38f81da561e889c60fca244cd
commit: 2f4eb34bd23d57d38f81da561e889c60fca244cd
branch: main
author: Pablo Galindo Salgado <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-03-20T12:47:59Z
summary:
gh-146171: Fix nested AttributeError suggestions (#146188)
files:
A Misc/NEWS.d/next/Library/2026-03-20-00-28-00.gh-issue-146171.P5Jk2R7v.rst
M Lib/test/test_traceback.py
M Lib/traceback.py
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 14a08995bf127c..7124e49b22e361 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -4420,19 +4420,21 @@ def __init__(self):
self.assertNotIn("inner.foo", actual)
def test_getattr_nested_with_property(self):
- # Test that descriptors (including properties) are suggested in nested
attributes
+ # Property suggestions should not execute the property getter.
class Inner:
@property
def computed(self):
- return 42
+ return missing_name
class Outer:
def __init__(self):
self.inner = Inner()
actual = self.get_suggestion(Outer(), 'computed')
- # Descriptors should not be suggested to avoid executing arbitrary code
- self.assertIn("inner.computed", actual)
+ self.assertIn(
+ "Did you mean '.inner.computed' instead of '.computed'",
+ actual,
+ )
def test_getattr_nested_no_suggestion_for_deep_nesting(self):
# Test that deeply nested attributes (2+ levels) are not suggested
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 956cab49131990..56a72ce7f5b293 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -1670,16 +1670,20 @@ def _check_for_nested_attribute(obj, wrong_name, attrs):
Returns the first nested attribute suggestion found, or None.
Limited to checking 20 attributes.
- Only considers non-descriptor attributes to avoid executing arbitrary code.
+ Only considers non-descriptor outer attributes to avoid executing
+ arbitrary code. Checks nested attributes statically so descriptors such
+ as properties can still be suggested without invoking them.
Skips lazy imports to avoid triggering module loading.
"""
+ from inspect import getattr_static
+
# Check for nested attributes (only one level deep)
attrs_to_check = [x for x in attrs if not x.startswith('_')][:20] # Limit
number of attributes to check
for attr_name in attrs_to_check:
with suppress(Exception):
# Check if attr_name is a descriptor - if so, skip it
- attr_from_class = getattr(type(obj), attr_name, None)
- if attr_from_class is not None and hasattr(attr_from_class,
'__get__'):
+ attr_from_class = getattr_static(type(obj), attr_name, _sentinel)
+ if attr_from_class is not _sentinel and hasattr(attr_from_class,
'__get__'):
continue # Skip descriptors to avoid executing arbitrary code
# Skip lazy imports to avoid triggering module loading
@@ -1689,10 +1693,10 @@ def _check_for_nested_attribute(obj, wrong_name, attrs):
# Safe to get the attribute since it's not a descriptor
attr_obj = getattr(obj, attr_name)
- # Check if the nested attribute exists and is not a descriptor
- nested_attr_from_class = getattr(type(attr_obj), wrong_name, None)
+ if _is_lazy_import(attr_obj, wrong_name):
+ continue
- if hasattr(attr_obj, wrong_name):
+ if getattr_static(attr_obj, wrong_name, _sentinel) is not
_sentinel:
return f"{attr_name}.{wrong_name}"
return None
diff --git
a/Misc/NEWS.d/next/Library/2026-03-20-00-28-00.gh-issue-146171.P5Jk2R7v.rst
b/Misc/NEWS.d/next/Library/2026-03-20-00-28-00.gh-issue-146171.P5Jk2R7v.rst
new file mode 100644
index 00000000000000..9514085bd3d27b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-03-20-00-28-00.gh-issue-146171.P5Jk2R7v.rst
@@ -0,0 +1,2 @@
+Nested :exc:`AttributeError` suggestions now include property-backed
+attributes on nested objects without executing the property getter.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]