Skip to content

Commit 5049b8f

Browse files
picnixzambv
andcommitted
[3.14] gh-139933: correctly suggest attributes for classes with a custom __dir__ (GH-139950)
(cherry picked from commit 4722202) Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 717ebd7 commit 5049b8f

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
@@ -4168,6 +4168,27 @@ def method(self, name):
41684168
self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, '_luch')))
41694169
self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, 'bluch')))
41704170

4171+
def test_getattr_suggestions_with_custom___dir__(self):
4172+
class M(type):
4173+
def __dir__(cls):
4174+
return [None, "fox"]
4175+
4176+
class C0:
4177+
def __dir__(self):
4178+
return [..., "bluch"]
4179+
4180+
class C1(C0, metaclass=M):
4181+
pass
4182+
4183+
self.assertNotIn("'bluch'", self.get_suggestion(C0, "blach"))
4184+
self.assertIn("'bluch'", self.get_suggestion(C0(), "blach"))
4185+
4186+
self.assertIn("'fox'", self.get_suggestion(C1, "foo"))
4187+
self.assertNotIn("'fox'", self.get_suggestion(C1(), "foo"))
4188+
4189+
self.assertNotIn("'bluch'", self.get_suggestion(C1, "blach"))
4190+
self.assertIn("'bluch'", self.get_suggestion(C1(), "blach"))
4191+
41714192
def test_getattr_suggestions_do_not_trigger_for_long_attributes(self):
41724193
class A:
41734194
blech = None

Lib/traceback.py

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

15911591

1592+
def _get_safe___dir__(obj):
1593+
# Use obj.__dir__() to avoid a TypeError when calling dir(obj).
1594+
# See gh-131001 and gh-139933.
1595+
try:
1596+
d = obj.__dir__()
1597+
except TypeError: # when obj is a class
1598+
d = type(obj).__dir__(obj)
1599+
return sorted(x for x in d if isinstance(x, str))
1600+
1601+
15921602
def _compute_suggestion_error(exc_value, tb, wrong_name):
15931603
if wrong_name is None or not isinstance(wrong_name, str):
15941604
return None
15951605
if isinstance(exc_value, AttributeError):
15961606
obj = exc_value.obj
15971607
try:
1598-
try:
1599-
d = dir(obj)
1600-
except TypeError: # Attributes are unsortable, e.g. int and str
1601-
d = list(obj.__class__.__dict__.keys()) + list(obj.__dict__.keys())
1602-
d = sorted([x for x in d if isinstance(x, str)])
1608+
d = _get_safe___dir__(obj)
16031609
hide_underscored = (wrong_name[:1] != '_')
16041610
if hide_underscored and tb is not None:
16051611
while tb.tb_next is not None:
@@ -1614,11 +1620,7 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
16141620
elif isinstance(exc_value, ImportError):
16151621
try:
16161622
mod = __import__(exc_value.name)
1617-
try:
1618-
d = dir(mod)
1619-
except TypeError: # Attributes are unsortable, e.g. int and str
1620-
d = list(mod.__dict__.keys())
1621-
d = sorted([x for x in d if isinstance(x, str)])
1623+
d = _get_safe___dir__(mod)
16221624
if wrong_name[:1] != '_':
16231625
d = [x for x in d if x[:1] != '_']
16241626
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)