Skip to content

Commit 652970c

Browse files
committed
correctly suggest attributes for classes with a custom __dir__
1 parent d4e5802 commit 652970c

File tree

3 files changed

+36
-10
lines changed

3 files changed

+36
-10
lines changed

Lib/test/test_traceback.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4146,6 +4146,27 @@ def method(self, name):
41464146
self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, '_luch')))
41474147
self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, 'bluch')))
41484148

4149+
def test_suggestions_with_custom___dir__(self):
4150+
class M(type):
4151+
def __dir__(cls):
4152+
return [None, "fox"]
4153+
4154+
class C0:
4155+
def __dir__(self):
4156+
return [..., "bluch"]
4157+
4158+
class C1(C0, metaclass=M):
4159+
pass
4160+
4161+
self.assertNotIn("'bluch'", self.get_suggestion(C0, "blach"))
4162+
self.assertIn("'bluch'", self.get_suggestion(C0(), "blach"))
4163+
4164+
self.assertIn("'fox'", self.get_suggestion(C1, "foo"))
4165+
self.assertNotIn("'fox'", self.get_suggestion(C1(), "foo"))
4166+
4167+
self.assertNotIn("'bluch'", self.get_suggestion(C1, "blach"))
4168+
self.assertIn("'bluch'", self.get_suggestion(C1(), "blach"))
4169+
41494170

41504171
def test_do_not_trigger_for_long_attributes(self):
41514172
class A:

Lib/traceback.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,17 +1629,23 @@ def _check_for_nested_attribute(obj, wrong_name, attrs):
16291629
return None
16301630

16311631

1632+
def _get_safe___dir__(obj):
1633+
# Use obj.__dir__() to avoid a TypeError when calling dir(obj).
1634+
# See gh-131001 and gh-139933.
1635+
try:
1636+
d = obj.__dir__()
1637+
except TypeError: # when obj is a class
1638+
d = type(obj).__dir__(obj)
1639+
return sorted(x for x in d if isinstance(x, str))
1640+
1641+
16321642
def _compute_suggestion_error(exc_value, tb, wrong_name):
16331643
if wrong_name is None or not isinstance(wrong_name, str):
16341644
return None
16351645
if isinstance(exc_value, AttributeError):
16361646
obj = exc_value.obj
16371647
try:
1638-
try:
1639-
d = dir(obj)
1640-
except TypeError: # Attributes are unsortable, e.g. int and str
1641-
d = list(obj.__class__.__dict__.keys()) + list(obj.__dict__.keys())
1642-
d = sorted([x for x in d if isinstance(x, str)])
1648+
d = _get_safe___dir__(obj)
16431649
hide_underscored = (wrong_name[:1] != '_')
16441650
if hide_underscored and tb is not None:
16451651
while tb.tb_next is not None:
@@ -1654,11 +1660,7 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
16541660
elif isinstance(exc_value, ImportError):
16551661
try:
16561662
mod = __import__(exc_value.name)
1657-
try:
1658-
d = dir(mod)
1659-
except TypeError: # Attributes are unsortable, e.g. int and str
1660-
d = list(mod.__dict__.keys())
1661-
d = sorted([x for x in d if isinstance(x, str)])
1663+
d = _get_safe___dir__(mod)
16621664
if wrong_name[:1] != '_':
16631665
d = [x for x in d if x[:1] != '_']
16641666
except Exception:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Improve :exc:`AttributeError` suggestions for classes with a custom
2+
:meth:`~object.__dir__` method returning a list of unsortable values.
3+
Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)