From 0085c3ae8f067abd4f6540d0f6dd2fb13107618e Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 6 May 2024 15:53:15 +0300 Subject: [PATCH] gh-116871: Improve name suggestions in tracebacks (GH-116930) Only include underscored names in name suggestions for AttributeError and ImportError if the original name was underscored. --- Lib/test/test_traceback.py | 32 +++++++++++++++++++ Lib/traceback.py | 11 +++++++ ...-03-17-18-24-23.gh-issue-116871.9uSl8M.rst | 2 ++ 3 files changed, 45 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-03-17-18-24-23.gh-issue-116871.9uSl8M.rst diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 7e4851058e0..5987ec382e6 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -3882,6 +3882,27 @@ class SuggestionFormattingTestBase: actual = self.get_suggestion(cls(), 'bluch') self.assertIn(suggestion, actual) + def test_getattr_suggestions_underscored(self): + class A: + bluch = None + + self.assertIn("'bluch'", self.get_suggestion(A(), 'blach')) + self.assertIn("'bluch'", self.get_suggestion(A(), '_luch')) + self.assertIn("'bluch'", self.get_suggestion(A(), '_bluch')) + + class B: + _bluch = None + def method(self, name): + getattr(self, name) + + self.assertIn("'_bluch'", self.get_suggestion(B(), '_blach')) + self.assertIn("'_bluch'", self.get_suggestion(B(), '_luch')) + self.assertNotIn("'_bluch'", self.get_suggestion(B(), 'bluch')) + + self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, '_blach'))) + self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, '_luch'))) + self.assertIn("'_bluch'", self.get_suggestion(partial(B().method, 'bluch'))) + def test_getattr_suggestions_do_not_trigger_for_long_attributes(self): class A: blech = None @@ -4074,6 +4095,17 @@ class SuggestionFormattingTestBase: actual = self.get_import_from_suggestion(code, 'bluch') self.assertIn(suggestion, actual) + def test_import_from_suggestions_underscored(self): + code = "bluch = None" + self.assertIn("'bluch'", self.get_import_from_suggestion(code, 'blach')) + self.assertIn("'bluch'", self.get_import_from_suggestion(code, '_luch')) + self.assertIn("'bluch'", self.get_import_from_suggestion(code, '_bluch')) + + code = "_bluch = None" + self.assertIn("'_bluch'", self.get_import_from_suggestion(code, '_blach')) + self.assertIn("'_bluch'", self.get_import_from_suggestion(code, '_luch')) + self.assertNotIn("'_bluch'", self.get_import_from_suggestion(code, 'bluch')) + def test_import_from_suggestions_do_not_trigger_for_long_attributes(self): code = "blech = None" diff --git a/Lib/traceback.py b/Lib/traceback.py index 1878779e154..9401b461497 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1469,12 +1469,23 @@ def _compute_suggestion_error(exc_value, tb, wrong_name): obj = exc_value.obj try: d = dir(obj) + hide_underscored = (wrong_name[:1] != '_') + if hide_underscored and tb is not None: + while tb.tb_next is not None: + tb = tb.tb_next + frame = tb.tb_frame + if 'self' in frame.f_locals and frame.f_locals['self'] is obj: + hide_underscored = False + if hide_underscored: + d = [x for x in d if x[:1] != '_'] except Exception: return None elif isinstance(exc_value, ImportError): try: mod = __import__(exc_value.name) d = dir(mod) + if wrong_name[:1] != '_': + d = [x for x in d if x[:1] != '_'] except Exception: return None else: diff --git a/Misc/NEWS.d/next/Library/2024-03-17-18-24-23.gh-issue-116871.9uSl8M.rst b/Misc/NEWS.d/next/Library/2024-03-17-18-24-23.gh-issue-116871.9uSl8M.rst new file mode 100644 index 00000000000..f3caa63187d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-17-18-24-23.gh-issue-116871.9uSl8M.rst @@ -0,0 +1,2 @@ +Name suggestions for :exc:`AttributeError` and :exc:`ImportError` now only +include underscored names if the original name was underscored.