mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +00:00
bpo-38530: Offer suggestions on AttributeError (#16856)
When printing AttributeError, PyErr_Display will offer suggestions of similar attribute names in the object that the exception was raised from: >>> collections.namedtoplo Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: module 'collections' has no attribute 'namedtoplo'. Did you mean: namedtuple?
This commit is contained in:
parent
3bc694d5f3
commit
37494b441a
12 changed files with 472 additions and 17 deletions
|
@ -1414,6 +1414,165 @@ class ExceptionTests(unittest.TestCase):
|
|||
gc_collect()
|
||||
|
||||
|
||||
class AttributeErrorTests(unittest.TestCase):
|
||||
def test_attributes(self):
|
||||
# Setting 'attr' should not be a problem.
|
||||
exc = AttributeError('Ouch!')
|
||||
self.assertIsNone(exc.name)
|
||||
self.assertIsNone(exc.obj)
|
||||
|
||||
sentinel = object()
|
||||
exc = AttributeError('Ouch', name='carry', obj=sentinel)
|
||||
self.assertEqual(exc.name, 'carry')
|
||||
self.assertIs(exc.obj, sentinel)
|
||||
|
||||
def test_getattr_has_name_and_obj(self):
|
||||
class A:
|
||||
blech = None
|
||||
|
||||
obj = A()
|
||||
try:
|
||||
obj.bluch
|
||||
except AttributeError as exc:
|
||||
self.assertEqual("bluch", exc.name)
|
||||
self.assertEqual(obj, exc.obj)
|
||||
|
||||
def test_getattr_has_name_and_obj_for_method(self):
|
||||
class A:
|
||||
def blech(self):
|
||||
return
|
||||
|
||||
obj = A()
|
||||
try:
|
||||
obj.bluch()
|
||||
except AttributeError as exc:
|
||||
self.assertEqual("bluch", exc.name)
|
||||
self.assertEqual(obj, exc.obj)
|
||||
|
||||
def test_getattr_suggestions(self):
|
||||
class Substitution:
|
||||
noise = more_noise = a = bc = None
|
||||
blech = None
|
||||
|
||||
class Elimination:
|
||||
noise = more_noise = a = bc = None
|
||||
blch = None
|
||||
|
||||
class Addition:
|
||||
noise = more_noise = a = bc = None
|
||||
bluchin = None
|
||||
|
||||
class SubstitutionOverElimination:
|
||||
blach = None
|
||||
bluc = None
|
||||
|
||||
class SubstitutionOverAddition:
|
||||
blach = None
|
||||
bluchi = None
|
||||
|
||||
class EliminationOverAddition:
|
||||
blucha = None
|
||||
bluc = None
|
||||
|
||||
for cls, suggestion in [(Substitution, "blech?"),
|
||||
(Elimination, "blch?"),
|
||||
(Addition, "bluchin?"),
|
||||
(EliminationOverAddition, "bluc?"),
|
||||
(SubstitutionOverElimination, "blach?"),
|
||||
(SubstitutionOverAddition, "blach?")]:
|
||||
try:
|
||||
cls().bluch
|
||||
except AttributeError as exc:
|
||||
with support.captured_stderr() as err:
|
||||
sys.__excepthook__(*sys.exc_info())
|
||||
|
||||
self.assertIn(suggestion, err.getvalue())
|
||||
|
||||
def test_getattr_suggestions_do_not_trigger_for_long_attributes(self):
|
||||
class A:
|
||||
blech = None
|
||||
|
||||
try:
|
||||
A().somethingverywrong
|
||||
except AttributeError as exc:
|
||||
with support.captured_stderr() as err:
|
||||
sys.__excepthook__(*sys.exc_info())
|
||||
|
||||
self.assertNotIn("blech", err.getvalue())
|
||||
|
||||
def test_getattr_suggestions_do_not_trigger_for_big_dicts(self):
|
||||
class A:
|
||||
blech = None
|
||||
# A class with a very big __dict__ will not be consider
|
||||
# for suggestions.
|
||||
for index in range(101):
|
||||
setattr(A, f"index_{index}", None)
|
||||
|
||||
try:
|
||||
A().bluch
|
||||
except AttributeError as exc:
|
||||
with support.captured_stderr() as err:
|
||||
sys.__excepthook__(*sys.exc_info())
|
||||
|
||||
self.assertNotIn("blech", err.getvalue())
|
||||
|
||||
def test_getattr_suggestions_no_args(self):
|
||||
class A:
|
||||
blech = None
|
||||
def __getattr__(self, attr):
|
||||
raise AttributeError()
|
||||
|
||||
try:
|
||||
A().bluch
|
||||
except AttributeError as exc:
|
||||
with support.captured_stderr() as err:
|
||||
sys.__excepthook__(*sys.exc_info())
|
||||
|
||||
self.assertIn("blech", err.getvalue())
|
||||
|
||||
class A:
|
||||
blech = None
|
||||
def __getattr__(self, attr):
|
||||
raise AttributeError
|
||||
|
||||
try:
|
||||
A().bluch
|
||||
except AttributeError as exc:
|
||||
with support.captured_stderr() as err:
|
||||
sys.__excepthook__(*sys.exc_info())
|
||||
|
||||
self.assertIn("blech", err.getvalue())
|
||||
|
||||
def test_getattr_suggestions_invalid_args(self):
|
||||
class NonStringifyClass:
|
||||
__str__ = None
|
||||
__repr__ = None
|
||||
|
||||
class A:
|
||||
blech = None
|
||||
def __getattr__(self, attr):
|
||||
raise AttributeError(NonStringifyClass())
|
||||
|
||||
class B:
|
||||
blech = None
|
||||
def __getattr__(self, attr):
|
||||
raise AttributeError("Error", 23)
|
||||
|
||||
class C:
|
||||
blech = None
|
||||
def __getattr__(self, attr):
|
||||
raise AttributeError(23)
|
||||
|
||||
for cls in [A, B, C]:
|
||||
try:
|
||||
cls().bluch
|
||||
except AttributeError as exc:
|
||||
with support.captured_stderr() as err:
|
||||
sys.__excepthook__(*sys.exc_info())
|
||||
|
||||
self.assertIn("blech", err.getvalue())
|
||||
|
||||
|
||||
class ImportErrorTests(unittest.TestCase):
|
||||
|
||||
def test_attributes(self):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue