bpo-38530: Require 50% similarity in NameError and AttributeError suggestions (GH-25584)

diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index b3c00b0..590935c 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -1507,6 +1507,61 @@ def f():
 
         self.assertNotIn("somethingverywronghehe", err.getvalue())
 
+    def test_name_error_bad_suggestions_do_not_trigger_for_small_names(self):
+        vvv = mom = w = id = pytho = None
+
+        with self.subTest(name="b"):
+            try:
+                b
+            except NameError as exc:
+                with support.captured_stderr() as err:
+                    sys.__excepthook__(*sys.exc_info())
+            self.assertNotIn("you mean", err.getvalue())
+            self.assertNotIn("vvv", err.getvalue())
+            self.assertNotIn("mom", err.getvalue())
+            self.assertNotIn("'id'", err.getvalue())
+            self.assertNotIn("'w'", err.getvalue())
+            self.assertNotIn("'pytho'", err.getvalue())
+
+        with self.subTest(name="v"):
+            try:
+                v
+            except NameError as exc:
+                with support.captured_stderr() as err:
+                    sys.__excepthook__(*sys.exc_info())
+            self.assertNotIn("you mean", err.getvalue())
+            self.assertNotIn("vvv", err.getvalue())
+            self.assertNotIn("mom", err.getvalue())
+            self.assertNotIn("'id'", err.getvalue())
+            self.assertNotIn("'w'", err.getvalue())
+            self.assertNotIn("'pytho'", err.getvalue())
+
+        with self.subTest(name="m"):
+            try:
+                m
+            except NameError as exc:
+                with support.captured_stderr() as err:
+                    sys.__excepthook__(*sys.exc_info())
+            self.assertNotIn("you mean", err.getvalue())
+            self.assertNotIn("vvv", err.getvalue())
+            self.assertNotIn("mom", err.getvalue())
+            self.assertNotIn("'id'", err.getvalue())
+            self.assertNotIn("'w'", err.getvalue())
+            self.assertNotIn("'pytho'", err.getvalue())
+
+        with self.subTest(name="py"):
+            try:
+                py
+            except NameError as exc:
+                with support.captured_stderr() as err:
+                    sys.__excepthook__(*sys.exc_info())
+            self.assertNotIn("you mean", err.getvalue())
+            self.assertNotIn("vvv", err.getvalue())
+            self.assertNotIn("mom", err.getvalue())
+            self.assertNotIn("'id'", err.getvalue())
+            self.assertNotIn("'w'", err.getvalue())
+            self.assertNotIn("'pytho'", err.getvalue())
+
     def test_name_error_suggestions_do_not_trigger_for_too_many_locals(self):
         def f():
             # Mutating locals() is unreliable, so we need to do it by hand
@@ -1661,6 +1716,63 @@ class A:
 
         self.assertNotIn("blech", err.getvalue())
 
+    def test_getattr_error_bad_suggestions_do_not_trigger_for_small_names(self):
+        class MyClass:
+            vvv = mom = w = id = pytho = None
+
+        with self.subTest(name="b"):
+            try:
+                MyClass.b
+            except AttributeError as exc:
+                with support.captured_stderr() as err:
+                    sys.__excepthook__(*sys.exc_info())
+            self.assertNotIn("you mean", err.getvalue())
+            self.assertNotIn("vvv", err.getvalue())
+            self.assertNotIn("mom", err.getvalue())
+            self.assertNotIn("'id'", err.getvalue())
+            self.assertNotIn("'w'", err.getvalue())
+            self.assertNotIn("'pytho'", err.getvalue())
+
+        with self.subTest(name="v"):
+            try:
+                MyClass.v
+            except AttributeError as exc:
+                with support.captured_stderr() as err:
+                    sys.__excepthook__(*sys.exc_info())
+            self.assertNotIn("you mean", err.getvalue())
+            self.assertNotIn("vvv", err.getvalue())
+            self.assertNotIn("mom", err.getvalue())
+            self.assertNotIn("'id'", err.getvalue())
+            self.assertNotIn("'w'", err.getvalue())
+            self.assertNotIn("'pytho'", err.getvalue())
+
+        with self.subTest(name="m"):
+            try:
+                MyClass.m
+            except AttributeError as exc:
+                with support.captured_stderr() as err:
+                    sys.__excepthook__(*sys.exc_info())
+            self.assertNotIn("you mean", err.getvalue())
+            self.assertNotIn("vvv", err.getvalue())
+            self.assertNotIn("mom", err.getvalue())
+            self.assertNotIn("'id'", err.getvalue())
+            self.assertNotIn("'w'", err.getvalue())
+            self.assertNotIn("'pytho'", err.getvalue())
+
+        with self.subTest(name="py"):
+            try:
+                MyClass.py
+            except AttributeError as exc:
+                with support.captured_stderr() as err:
+                    sys.__excepthook__(*sys.exc_info())
+            self.assertNotIn("you mean", err.getvalue())
+            self.assertNotIn("vvv", err.getvalue())
+            self.assertNotIn("mom", err.getvalue())
+            self.assertNotIn("'id'", err.getvalue())
+            self.assertNotIn("'w'", err.getvalue())
+            self.assertNotIn("'pytho'", err.getvalue())
+
+
     def test_getattr_suggestions_do_not_trigger_for_big_dicts(self):
         class A:
             blech = None
diff --git a/Python/suggestions.c b/Python/suggestions.c
index d4e9dc2..2fd6714 100644
--- a/Python/suggestions.c
+++ b/Python/suggestions.c
@@ -102,7 +102,10 @@ calculate_suggestions(PyObject *dir,
         if (current_distance == -1) {
             return NULL;
         }
-        if (current_distance == 0 || current_distance > MAX_DISTANCE) {
+        if (current_distance == 0 ||
+            current_distance > MAX_DISTANCE ||
+            current_distance * 2 > name_size)
+        {
             continue;
         }
         if (!suggestion || current_distance < suggestion_distance) {