bpo-41031: Match C and Python code formatting of unprintable exceptions and exceptions in the __main__ module. (GH-28139)

diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 12305ca..e98803b 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1071,19 +1071,29 @@ def __del__(self):
                 self.assertTrue(report.endswith("\n"))
 
     def test_original_unraisablehook_exception_qualname(self):
+        # See bpo-41031, bpo-45083.
+        # Check that the exception is printed with its qualified name
+        # rather than just classname, and the module names appears
+        # unless it is one of the hard-coded exclusions.
         class A:
             class B:
                 class X(Exception):
                     pass
 
-        with test.support.captured_stderr() as stderr, \
-             test.support.swap_attr(sys, 'unraisablehook',
-                                    sys.__unraisablehook__):
-                 expected = self.write_unraisable_exc(
-                     A.B.X(), "msg", "obj");
-        report = stderr.getvalue()
-        testName = 'test_original_unraisablehook_exception_qualname'
-        self.assertIn(f"{testName}.<locals>.A.B.X", report)
+        for moduleName in 'builtins', '__main__', 'some_module':
+            with self.subTest(moduleName=moduleName):
+                A.B.X.__module__ = moduleName
+                with test.support.captured_stderr() as stderr, \
+                     test.support.swap_attr(sys, 'unraisablehook',
+                                            sys.__unraisablehook__):
+                         expected = self.write_unraisable_exc(
+                             A.B.X(), "msg", "obj");
+                report = stderr.getvalue()
+                self.assertIn(A.B.X.__qualname__, report)
+                if moduleName in ['builtins', '__main__']:
+                    self.assertNotIn(moduleName + '.', report)
+                else:
+                    self.assertIn(moduleName + '.', report)
 
     def test_original_unraisablehook_wrong_type(self):
         exc = ValueError(42)
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 949adef..363165d 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -172,7 +172,7 @@ def __str__(self):
                 1/0
         err = traceback.format_exception_only(X, X())
         self.assertEqual(len(err), 1)
-        str_value = '<unprintable %s object>' % X.__name__
+        str_value = '<exception str() failed>'
         if X.__module__ in ('__main__', 'builtins'):
             str_name = X.__qualname__
         else:
@@ -1171,19 +1171,45 @@ def test_syntax_error_various_offsets(self):
                 exp = "\n".join(expected)
                 self.assertEqual(exp, err)
 
-    def test_format_exception_only_qualname(self):
+    def test_exception_qualname(self):
         class A:
             class B:
                 class X(Exception):
                     def __str__(self):
                         return "I am X"
-                    pass
+
         err = self.get_report(A.B.X())
         str_value = 'I am X'
         str_name = '.'.join([A.B.X.__module__, A.B.X.__qualname__])
         exp = "%s: %s\n" % (str_name, str_value)
         self.assertEqual(exp, err)
 
+    def test_exception_modulename(self):
+        class X(Exception):
+            def __str__(self):
+                return "I am X"
+
+        for modulename in '__main__', 'builtins', 'some_module':
+            X.__module__ = modulename
+            with self.subTest(modulename=modulename):
+                err = self.get_report(X())
+                str_value = 'I am X'
+                if modulename in ['builtins', '__main__']:
+                    str_name = X.__qualname__
+                else:
+                    str_name = '.'.join([X.__module__, X.__qualname__])
+                exp = "%s: %s\n" % (str_name, str_value)
+                self.assertEqual(exp, err)
+
+    def test_exception_bad__str__(self):
+        class X(Exception):
+            def __str__(self):
+                1/0
+        err = self.get_report(X())
+        str_value = '<exception str() failed>'
+        str_name = '.'.join([X.__module__, X.__qualname__])
+        self.assertEqual(err, f"{str_name}: {str_value}\n")
+
 
 class PyExcReportingTests(BaseExceptionReportingTests, unittest.TestCase):
     #
diff --git a/Lib/traceback.py b/Lib/traceback.py
index d51c201..1b537dc 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -169,7 +169,7 @@ def _some_str(value):
     try:
         return str(value)
     except:
-        return '<unprintable %s object>' % type(value).__name__
+        return '<exception str() failed>'
 
 # --
 
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-09-03-12-35-17.bpo-41031.yPSJEs.rst b/Misc/NEWS.d/next/Core and Builtins/2021-09-03-12-35-17.bpo-41031.yPSJEs.rst
new file mode 100644
index 0000000..5dcfaa0
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2021-09-03-12-35-17.bpo-41031.yPSJEs.rst
@@ -0,0 +1 @@
+Match C and Python code formatting of unprintable exceptions and exceptions in the :mod:`__main__` module.
\ No newline at end of file
diff --git a/Python/errors.c b/Python/errors.c
index 15ca21b..b2030f7 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -25,6 +25,7 @@ extern char *strerror(int);
 extern "C" {
 #endif
 
+_Py_IDENTIFIER(__main__);
 _Py_IDENTIFIER(__module__);
 _Py_IDENTIFIER(builtins);
 _Py_IDENTIFIER(stderr);
@@ -1297,7 +1298,8 @@ write_unraisable_exc_file(PyThreadState *tstate, PyObject *exc_type,
         }
     }
     else {
-        if (!_PyUnicode_EqualToASCIIId(modulename, &PyId_builtins)) {
+        if (!_PyUnicode_EqualToASCIIId(modulename, &PyId_builtins) &&
+            !_PyUnicode_EqualToASCIIId(modulename, &PyId___main__)) {
             if (PyFile_WriteObject(modulename, file, Py_PRINT_RAW) < 0) {
                 Py_DECREF(modulename);
                 return -1;
diff --git a/Python/pythonrun.c b/Python/pythonrun.c
index 3d07f43..0e0262c 100644
--- a/Python/pythonrun.c
+++ b/Python/pythonrun.c
@@ -35,6 +35,7 @@
 #endif
 
 
+_Py_IDENTIFIER(__main__);
 _Py_IDENTIFIER(builtins);
 _Py_IDENTIFIER(excepthook);
 _Py_IDENTIFIER(flush);
@@ -974,7 +975,8 @@ print_exception(PyObject *f, PyObject *value)
             err = PyFile_WriteString("<unknown>", f);
         }
         else {
-            if (!_PyUnicode_EqualToASCIIId(modulename, &PyId_builtins))
+            if (!_PyUnicode_EqualToASCIIId(modulename, &PyId_builtins) &&
+                !_PyUnicode_EqualToASCIIId(modulename, &PyId___main__))
             {
                 err = PyFile_WriteObject(modulename, f, Py_PRINT_RAW);
                 err += PyFile_WriteString(".", f);