]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-139933: correctly suggest attributes for classes with a custom `__dir__` (GH-139950)
authorBénédikt Tran <10796600+picnixz@users.noreply.github.com>
Wed, 11 Mar 2026 14:30:09 +0000 (15:30 +0100)
committerGitHub <noreply@github.com>
Wed, 11 Mar 2026 14:30:09 +0000 (15:30 +0100)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
Lib/test/test_traceback.py
Lib/traceback.py
Misc/NEWS.d/next/Library/2025-10-11-11-50-59.gh-issue-139933.05MHlx.rst [new file with mode: 0644]

index 2fbc2a041269f4a59dd0406fcd335ac25a761a07..14a08995bf127c43a17fe9dc0465b4fc29628211 100644 (file)
@@ -4213,6 +4213,27 @@ class BaseSuggestionTests(SuggestionFormattingTestMixin):
         self.assertIn("'._bluch'", self.get_suggestion(partial(B().method, '_luch')))
         self.assertIn("'._bluch'", self.get_suggestion(partial(B().method, 'bluch')))
 
+    def test_suggestions_with_custom___dir__(self):
+        class M(type):
+            def __dir__(cls):
+                return [None, "fox"]
+
+        class C0:
+            def __dir__(self):
+                return [..., "bluch"]
+
+        class C1(C0, metaclass=M):
+            pass
+
+        self.assertNotIn("'.bluch'", self.get_suggestion(C0, "blach"))
+        self.assertIn("'.bluch'", self.get_suggestion(C0(), "blach"))
+
+        self.assertIn("'.fox'", self.get_suggestion(C1, "foo"))
+        self.assertNotIn("'.fox'", self.get_suggestion(C1(), "foo"))
+
+        self.assertNotIn("'.bluch'", self.get_suggestion(C1, "blach"))
+        self.assertIn("'.bluch'", self.get_suggestion(C1(), "blach"))
+
 
     def test_do_not_trigger_for_long_attributes(self):
         class A:
index 4e809acb7a01bb51ffe54122751cd854a1c75bd9..956cab49131990c0bc74fc2699af660e89f784d9 100644 (file)
@@ -1698,6 +1698,19 @@ def _check_for_nested_attribute(obj, wrong_name, attrs):
     return None
 
 
+def _get_safe___dir__(obj):
+    # Use obj.__dir__() to avoid a TypeError when calling dir(obj).
+    # See gh-131001 and gh-139933.
+    # Also filters out lazy imports to avoid triggering module loading.
+    try:
+        d = obj.__dir__()
+    except TypeError:  # when obj is a class
+        d = type(obj).__dir__(obj)
+    return sorted(
+        x for x in d if isinstance(x, str) and not _is_lazy_import(obj, x)
+    )
+
+
 def _compute_suggestion_error(exc_value, tb, wrong_name):
     if wrong_name is None or not isinstance(wrong_name, str):
         return None
@@ -1711,13 +1724,7 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
     if isinstance(exc_value, AttributeError):
         obj = exc_value.obj
         try:
-            try:
-                d = dir(obj)
-            except TypeError:  # Attributes are unsortable, e.g. int and str
-                d = list(obj.__class__.__dict__.keys()) + list(obj.__dict__.keys())
-            d = sorted([x for x in d if isinstance(x, str)])
-            # Filter out lazy imports to avoid triggering module loading
-            d = [x for x in d if not _is_lazy_import(obj, x)]
+            d = _get_safe___dir__(obj)
             hide_underscored = (wrong_name[:1] != '_')
             if hide_underscored and tb is not None:
                 while tb.tb_next is not None:
@@ -1744,13 +1751,7 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
     elif isinstance(exc_value, ImportError):
         try:
             mod = __import__(exc_value.name)
-            try:
-                d = dir(mod)
-            except TypeError:  # Attributes are unsortable, e.g. int and str
-                d = list(mod.__dict__.keys())
-            d = sorted([x for x in d if isinstance(x, str)])
-            # Filter out lazy imports to avoid triggering module loading
-            d = [x for x in d if not _is_lazy_import(mod, x)]
+            d = _get_safe___dir__(mod)
             if wrong_name[:1] != '_':
                 d = [x for x in d if x[:1] != '_']
         except Exception:
diff --git a/Misc/NEWS.d/next/Library/2025-10-11-11-50-59.gh-issue-139933.05MHlx.rst b/Misc/NEWS.d/next/Library/2025-10-11-11-50-59.gh-issue-139933.05MHlx.rst
new file mode 100644 (file)
index 0000000..d76f087
--- /dev/null
@@ -0,0 +1,3 @@
+Improve :exc:`AttributeError` suggestions for classes with a custom
+:meth:`~object.__dir__` method returning a list of unsortable values.
+Patch by Bénédikt Tran.