bpo-38530: Offer suggestions on NameError (GH-25397)
When printing NameError raised by the interpreter, PyErr_Display
will offer suggestions of simmilar variable names in the function that the exception
was raised from:
>>> schwarzschild_black_hole = None
>>> schwarschild_black_hole
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'schwarschild_black_hole' is not defined. Did you mean: schwarzschild_black_hole?
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index e1a5ec7..4f3c9ab 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -1413,6 +1413,129 @@ class TestException(MemoryError):
gc_collect()
+global_for_suggestions = None
+
+class NameErrorTests(unittest.TestCase):
+ def test_name_error_has_name(self):
+ try:
+ bluch
+ except NameError as exc:
+ self.assertEqual("bluch", exc.name)
+
+ def test_name_error_suggestions(self):
+ def Substitution():
+ noise = more_noise = a = bc = None
+ blech = None
+ print(bluch)
+
+ def Elimination():
+ noise = more_noise = a = bc = None
+ blch = None
+ print(bluch)
+
+ def Addition():
+ noise = more_noise = a = bc = None
+ bluchin = None
+ print(bluch)
+
+ def SubstitutionOverElimination():
+ blach = None
+ bluc = None
+ print(bluch)
+
+ def SubstitutionOverAddition():
+ blach = None
+ bluchi = None
+ print(bluch)
+
+ def EliminationOverAddition():
+ blucha = None
+ bluc = None
+ print(bluch)
+
+ for func, suggestion in [(Substitution, "blech?"),
+ (Elimination, "blch?"),
+ (Addition, "bluchin?"),
+ (EliminationOverAddition, "blucha?"),
+ (SubstitutionOverElimination, "blach?"),
+ (SubstitutionOverAddition, "blach?")]:
+ err = None
+ try:
+ func()
+ except NameError as exc:
+ with support.captured_stderr() as err:
+ sys.__excepthook__(*sys.exc_info())
+ self.assertIn(suggestion, err.getvalue())
+
+ def test_name_error_suggestions_from_globals(self):
+ def func():
+ print(global_for_suggestio)
+ try:
+ func()
+ except NameError as exc:
+ with support.captured_stderr() as err:
+ sys.__excepthook__(*sys.exc_info())
+ self.assertIn("global_for_suggestions?", err.getvalue())
+
+ def test_name_error_suggestions_do_not_trigger_for_long_names(self):
+ def f():
+ somethingverywronghehehehehehe = None
+ print(somethingverywronghe)
+
+ try:
+ f()
+ except NameError as exc:
+ with support.captured_stderr() as err:
+ sys.__excepthook__(*sys.exc_info())
+
+ self.assertNotIn("somethingverywronghehe", err.getvalue())
+
+ def test_name_error_suggestions_do_not_trigger_for_big_dicts(self):
+ def f():
+ # Mutating locals() is unreliable, so we need to do it by hand
+ a1 = a2 = a3 = a4 = a5 = a6 = a7 = a8 = a9 = a10 = a11 = a12 = a13 = \
+ a14 = a15 = a16 = a17 = a18 = a19 = a20 = a21 = a22 = a23 = a24 = a25 = \
+ a26 = a27 = a28 = a29 = a30 = a31 = a32 = a33 = a34 = a35 = a36 = a37 = \
+ a38 = a39 = a40 = a41 = a42 = a43 = a44 = a45 = a46 = a47 = a48 = a49 = \
+ a50 = a51 = a52 = a53 = a54 = a55 = a56 = a57 = a58 = a59 = a60 = a61 = \
+ a62 = a63 = a64 = a65 = a66 = a67 = a68 = a69 = a70 = a71 = a72 = a73 = \
+ a74 = a75 = a76 = a77 = a78 = a79 = a80 = a81 = a82 = a83 = a84 = a85 = \
+ a86 = a87 = a88 = a89 = a90 = a91 = a92 = a93 = a94 = a95 = a96 = a97 = \
+ a98 = a99 = a100 = a101 = a102 = a103 = None
+ print(a0)
+
+ try:
+ f()
+ except NameError as exc:
+ with support.captured_stderr() as err:
+ sys.__excepthook__(*sys.exc_info())
+
+ self.assertNotIn("a10", err.getvalue())
+
+ def test_name_error_with_custom_exceptions(self):
+ def f():
+ blech = None
+ raise NameError()
+
+ try:
+ f()
+ except NameError as exc:
+ with support.captured_stderr() as err:
+ sys.__excepthook__(*sys.exc_info())
+
+ self.assertNotIn("blech", err.getvalue())
+
+ def f():
+ blech = None
+ raise NameError
+
+ try:
+ f()
+ except NameError as exc:
+ with support.captured_stderr() as err:
+ sys.__excepthook__(*sys.exc_info())
+
+ self.assertNotIn("blech", err.getvalue())
class AttributeErrorTests(unittest.TestCase):
def test_attributes(self):