Skip to content

Commit 881cc06

Browse files
ambvpicnixz
andauthored
[3.13] gh-139933: correctly suggest attributes for classes with a custom __dir__ (GH-139950) (GH-145827) (GH-145833)
(cherry picked from commit 4722202) (cherry picked from commit 0a80015) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent 17a9c26 commit 881cc06

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
@@ -4113,6 +4113,27 @@ def method(self, name):
41134113
self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, '_luch')))
41144114
self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, 'bluch')))
41154115

4116+
def test_getattr_suggestions_with_custom___dir__(self):
4117+
class M(type):
4118+
def __dir__(cls):
4119+
return [None, "fox"]
4120+
4121+
class C0:
4122+
def __dir__(self):
4123+
return [..., "bluch"]
4124+
4125+
class C1(C0, metaclass=M):
4126+
pass
4127+
4128+
self.assertNotIn("'bluch'", self.get_suggestion(C0, "blach"))
4129+
self.assertIn("'bluch'", self.get_suggestion(C0(), "blach"))
4130+
4131+
self.assertIn("'fox'", self.get_suggestion(C1, "foo"))
4132+
self.assertNotIn("'fox'", self.get_suggestion(C1(), "foo"))
4133+
4134+
self.assertNotIn("'bluch'", self.get_suggestion(C1, "blach"))
4135+
self.assertIn("'bluch'", self.get_suggestion(C1(), "blach"))
4136+
41164137
def test_getattr_suggestions_do_not_trigger_for_long_attributes(self):
41174138
class A:
41184139
blech = None

Lib/traceback.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,17 +1484,23 @@ def _substitution_cost(ch_a, ch_b):
14841484
return _MOVE_COST
14851485

14861486

1487+
def _get_safe___dir__(obj):
1488+
# Use obj.__dir__() to avoid a TypeError when calling dir(obj).
1489+
# See gh-131001 and gh-139933.
1490+
try:
1491+
d = obj.__dir__()
1492+
except TypeError: # when obj is a class
1493+
d = type(obj).__dir__(obj)
1494+
return sorted(x for x in d if isinstance(x, str))
1495+
1496+
14871497
def _compute_suggestion_error(exc_value, tb, wrong_name):
14881498
if wrong_name is None or not isinstance(wrong_name, str):
14891499
return None
14901500
if isinstance(exc_value, AttributeError):
14911501
obj = exc_value.obj
14921502
try:
1493-
try:
1494-
d = dir(obj)
1495-
except TypeError: # Attributes are unsortable, e.g. int and str
1496-
d = list(obj.__class__.__dict__.keys()) + list(obj.__dict__.keys())
1497-
d = sorted([x for x in d if isinstance(x, str)])
1503+
d = _get_safe___dir__(obj)
14981504
hide_underscored = (wrong_name[:1] != '_')
14991505
if hide_underscored and tb is not None:
15001506
while tb.tb_next is not None:
@@ -1509,11 +1515,7 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
15091515
elif isinstance(exc_value, ImportError):
15101516
try:
15111517
mod = __import__(exc_value.name)
1512-
try:
1513-
d = dir(mod)
1514-
except TypeError: # Attributes are unsortable, e.g. int and str
1515-
d = list(mod.__dict__.keys())
1516-
d = sorted([x for x in d if isinstance(x, str)])
1518+
d = _get_safe___dir__(mod)
15171519
if wrong_name[:1] != '_':
15181520
d = [x for x in d if x[:1] != '_']
15191521
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)