[Python-checkins] bpo-42955: Add sys.modules_names (GH-24238)

vstinner webhook-mailer at python.org
Mon Jan 25 07:25:15 EST 2021


https://github.com/python/cpython/commit/db584bdad32d81e42b71871077a8008036f5c048
commit: db584bdad32d81e42b71871077a8008036f5c048
branch: master
author: Victor Stinner <vstinner at python.org>
committer: vstinner <vstinner at python.org>
date: 2021-01-25T13:24:42+01:00
summary:

bpo-42955: Add sys.modules_names (GH-24238)

Add sys.module_names, containing the list of the standard library
module names.

files:
A Misc/NEWS.d/next/Library/2021-01-18-11-59-46.bpo-42955.CSWLC9.rst
M Doc/library/sys.rst
M Doc/whatsnew/3.10.rst
M Lib/test/test_capi.py
M Lib/test/test_faulthandler.py
M Lib/test/test_sys.py
M Python/module_names.h
M Python/pylifecycle.c
M Python/sysmodule.c
M Tools/scripts/generate_module_names.py

diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst
index 0f13adcf0e5b2..d536fc9322eb0 100644
--- a/Doc/library/sys.rst
+++ b/Doc/library/sys.rst
@@ -153,10 +153,12 @@ always available.
 
 .. data:: builtin_module_names
 
-   A tuple of strings giving the names of all modules that are compiled into this
+   A tuple of strings containing the names of all modules that are compiled into this
    Python interpreter.  (This information is not available in any other way ---
    ``modules.keys()`` only lists the imported modules.)
 
+   See also the :attr:`sys.module_names` list.
+
 
 .. function:: call_tracing(func, args)
 
@@ -1060,6 +1062,24 @@ always available.
         This is still called as a fallback if a :data:`meta_path` entry doesn't
         have a :meth:`~importlib.abc.MetaPathFinder.find_spec` method.
 
+.. data:: module_names
+
+   A frozenset of strings containing the names of standard library modules.
+
+   It is the same on all platforms. Modules which are not available on
+   some platforms and modules disabled at Python build are also listed.
+   All module kinds are listed: pure Python, built-in, frozen and extension
+   modules. Test modules are excluded.
+
+   For packages, only sub-packages are listed, not sub-modules. For example,
+   ``concurrent`` package and ``concurrent.futures`` sub-package are listed,
+   but not ``concurrent.futures.base`` sub-module.
+
+   See also the :attr:`sys.builtin_module_names` list.
+
+   .. versionchanged:: 3.10
+
+
 .. data:: modules
 
    This is a dictionary that maps module names to modules which have already been
diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst
index d822dda09d2e1..a6c3fbbff916e 100644
--- a/Doc/whatsnew/3.10.rst
+++ b/Doc/whatsnew/3.10.rst
@@ -396,6 +396,10 @@ Add :data:`sys.orig_argv` attribute: the list of the original command line
 arguments passed to the Python executable.
 (Contributed by Victor Stinner in :issue:`23427`.)
 
+Add :data:`sys.module_names`, containing the list of the standard library
+module names.
+(Contributed by Victor Stinner in :issue:`42955`.)
+
 threading
 ---------
 
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 67175cd044a21..5f5c0d038d9f3 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -569,12 +569,23 @@ def check_fatal_error(self, code, expected, not_expected=()):
         self.assertEqual(len(modules), total)
 
     def test_fatal_error(self):
+        # By default, stdlib extension modules are ignored,
+        # but not test modules.
         expected = ('_testcapi',)
-        not_expected = ('sys', 'builtins', '_imp', '_thread', '_weakref',
-                        '_io', 'marshal', '_signal', '_abc')
-        code = 'import _testcapi; _testcapi.fatal_error(b"MESSAGE")'
+        not_expected = ('sys',)
+        code = 'import _testcapi, sys; _testcapi.fatal_error(b"MESSAGE")'
         self.check_fatal_error(code, expected, not_expected)
 
+        # Mark _testcapi as stdlib module, but not sys
+        expected = ('sys',)
+        not_expected = ('_testcapi',)
+        code = textwrap.dedent('''
+            import _testcapi, sys
+            sys.module_names = frozenset({"_testcapi"})
+            _testcapi.fatal_error(b"MESSAGE")
+        ''')
+        self.check_fatal_error(code, expected)
+
 
 class TestPendingCalls(unittest.TestCase):
 
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
index b4a654f8a9cae..02077a69bb4d8 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -334,8 +334,9 @@ def test_disable(self):
     def test_dump_ext_modules(self):
         code = """
             import faulthandler
-            # _testcapi is a test module and not considered as a stdlib module
-            import _testcapi
+            import sys
+            # Don't filter stdlib module names
+            sys.module_names = frozenset()
             faulthandler.enable()
             faulthandler._sigsegv()
             """
@@ -346,7 +347,8 @@ def test_dump_ext_modules(self):
         if not match:
             self.fail(f"Cannot find 'Extension modules:' in {stderr!r}")
         modules = set(match.group(1).strip().split(', '))
-        self.assertIn('_testcapi', modules)
+        for name in ('sys', 'faulthandler'):
+            self.assertIn(name, modules)
 
     def test_is_enabled(self):
         orig_stderr = sys.stderr
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 3af5b117affde..729b8667fc857 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -986,6 +986,11 @@ def test_orig_argv(self):
         self.assertEqual(proc.stdout.rstrip().splitlines(), expected,
                          proc)
 
+    def test_module_names(self):
+        self.assertIsInstance(sys.module_names, frozenset)
+        for name in sys.module_names:
+            self.assertIsInstance(name, str)
+
 
 @test.support.cpython_only
 class UnraisableHookTest(unittest.TestCase):
diff --git a/Misc/NEWS.d/next/Library/2021-01-18-11-59-46.bpo-42955.CSWLC9.rst b/Misc/NEWS.d/next/Library/2021-01-18-11-59-46.bpo-42955.CSWLC9.rst
new file mode 100644
index 0000000000000..0631acd7a9870
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-01-18-11-59-46.bpo-42955.CSWLC9.rst
@@ -0,0 +1,2 @@
+Add :data:`sys.module_names`, containing the list of the standard library
+module names. Patch by Victor Stinner.
diff --git a/Python/module_names.h b/Python/module_names.h
index 533a73260efe9..0dc2633916d30 100644
--- a/Python/module_names.h
+++ b/Python/module_names.h
@@ -1,63 +1,123 @@
 // Auto-generated by Tools/scripts/generate_module_names.py.
+// List used to create sys.module_names.
 
 static const char* _Py_module_names[] = {
-
-// Built-in modules
+"__future__",
 "_abc",
+"_aix_support",
 "_ast",
+"_asyncio",
+"_bisect",
+"_blake2",
+"_bootsubprocess",
+"_bz2",
 "_codecs",
+"_codecs_cn",
+"_codecs_hk",
+"_codecs_iso2022",
+"_codecs_jp",
+"_codecs_kr",
+"_codecs_tw",
 "_collections",
+"_collections_abc",
+"_compat_pickle",
+"_compression",
+"_contextvars",
+"_crypt",
+"_csv",
+"_ctypes",
+"_curses",
+"_curses_panel",
+"_datetime",
+"_dbm",
+"_decimal",
+"_elementtree",
 "_functools",
+"_gdbm",
+"_hashlib",
+"_heapq",
 "_imp",
 "_io",
+"_json",
 "_locale",
+"_lsprof",
+"_lzma",
+"_markupbase",
+"_md5",
+"_msi",
+"_multibytecodec",
+"_multiprocessing",
+"_opcode",
 "_operator",
+"_osx_support",
+"_pickle",
+"_posixshmem",
+"_posixsubprocess",
+"_py_abc",
+"_pydecimal",
+"_pyio",
+"_queue",
+"_random",
+"_sha1",
+"_sha256",
+"_sha3",
+"_sha512",
 "_signal",
+"_sitebuiltins",
+"_socket",
+"_sqlite3",
 "_sre",
+"_ssl",
 "_stat",
+"_statistics",
 "_string",
+"_strptime",
+"_struct",
 "_symtable",
 "_thread",
+"_threading_local",
+"_tkinter",
 "_tracemalloc",
+"_uuid",
 "_warnings",
 "_weakref",
-"atexit",
-"builtins",
-"errno",
-"faulthandler",
-"gc",
-"itertools",
-"marshal",
-"posix",
-"pwd",
-"sys",
-"time",
-
-// Pure Python modules (Lib/*.py)
-"__future__",
+"_weakrefset",
+"_winapi",
+"_xxsubinterpreters",
+"_zoneinfo",
 "abc",
 "aifc",
 "antigravity",
 "argparse",
+"array",
 "ast",
 "asynchat",
+"asyncio",
 "asyncore",
+"atexit",
+"audioop",
 "base64",
 "bdb",
+"binascii",
 "binhex",
 "bisect",
+"builtins",
 "bz2",
 "cProfile",
 "calendar",
 "cgi",
 "cgitb",
 "chunk",
+"cmath",
 "cmd",
 "code",
 "codecs",
 "codeop",
+"collections",
 "colorsys",
 "compileall",
+"concurrent",
+"concurrent.futures",
 "configparser",
 "contextlib",
 "contextvars",
@@ -65,45 +125,80 @@ static const char* _Py_module_names[] = {
 "copyreg",
 "crypt",
 "csv",
+"ctypes",
+"ctypes.macholib",
+"curses",
 "dataclasses",
 "datetime",
+"dbm",
 "decimal",
 "difflib",
 "dis",
+"distutils",
+"distutils.command",
 "doctest",
+"email",
+"email.mime",
+"encodings",
+"ensurepip",
+"ensurepip._bundled",
 "enum",
+"errno",
+"faulthandler",
+"fcntl",
 "filecmp",
 "fileinput",
 "fnmatch",
 "fractions",
 "ftplib",
 "functools",
+"gc",
 "genericpath",
 "getopt",
 "getpass",
 "gettext",
 "glob",
 "graphlib",
+"grp",
 "gzip",
 "hashlib",
 "heapq",
 "hmac",
+"html",
+"http",
+"idlelib",
 "imaplib",
 "imghdr",
 "imp",
+"importlib",
 "inspect",
 "io",
 "ipaddress",
+"itertools",
+"json",
 "keyword",
+"lib2to3",
+"lib2to3.fixes",
+"lib2to3.pgen2",
 "linecache",
 "locale",
+"logging",
 "lzma",
 "mailbox",
 "mailcap",
+"marshal",
+"math",
 "mimetypes",
+"mmap",
 "modulefinder",
+"msilib",
+"msvcrt",
+"multiprocessing",
+"multiprocessing.dummy",
 "netrc",
+"nis",
 "nntplib",
+"nt",
 "ntpath",
 "nturl2path",
 "numbers",
@@ -111,6 +206,7 @@ static const char* _Py_module_names[] = {
 "operator",
 "optparse",
 "os",
+"ossaudiodev",
 "pathlib",
 "pdb",
 "pickle",
@@ -120,23 +216,30 @@ static const char* _Py_module_names[] = {
 "platform",
 "plistlib",
 "poplib",
+"posix",
 "posixpath",
 "pprint",
 "profile",
 "pstats",
 "pty",
+"pwd",
 "py_compile",
 "pyclbr",
 "pydoc",
+"pydoc_data",
+"pyexpat",
 "queue",
 "quopri",
 "random",
 "re",
+"readline",
 "reprlib",
+"resource",
 "rlcompleter",
 "runpy",
 "sched",
 "secrets",
+"select",
 "selectors",
 "shelve",
 "shlex",
@@ -148,6 +251,8 @@ static const char* _Py_module_names[] = {
 "sndhdr",
 "socket",
 "socketserver",
+"spwd",
+"sqlite3",
 "sre_compile",
 "sre_constants",
 "sre_parse",
@@ -160,15 +265,20 @@ static const char* _Py_module_names[] = {
 "subprocess",
 "sunau",
 "symtable",
+"sys",
 "sysconfig",
+"syslog",
 "tabnanny",
 "tarfile",
 "telnetlib",
 "tempfile",
+"termios",
 "textwrap",
 "this",
 "threading",
+"time",
 "timeit",
+"tkinter",
 "token",
 "tokenize",
 "trace",
@@ -176,161 +286,32 @@ static const char* _Py_module_names[] = {
 "tracemalloc",
 "tty",
 "turtle",
+"turtledemo",
 "types",
 "typing",
+"unicodedata",
+"unittest",
+"urllib",
 "uu",
 "uuid",
+"venv",
 "warnings",
 "wave",
 "weakref",
 "webbrowser",
-"xdrlib",
-"zipapp",
-"zipfile",
-"zipimport",
-
-// Packages and sub-packages
-"asyncio",
-"collections",
-"concurrent",
-"concurrent.futures",
-"ctypes",
-"ctypes.macholib",
-"curses",
-"dbm",
-"distutils",
-"distutils.command",
-"email",
-"email.mime",
-"encodings",
-"ensurepip",
-"ensurepip._bundled",
-"html",
-"http",
-"idlelib",
-"importlib",
-"json",
-"lib2to3",
-"lib2to3.fixes",
-"lib2to3.pgen2",
-"logging",
-"msilib",
-"multiprocessing",
-"multiprocessing.dummy",
-"pydoc_data",
-"sqlite3",
-"tkinter",
-"turtledemo",
-"unittest",
-"urllib",
-"venv",
+"winreg",
+"winsound",
 "wsgiref",
+"xdrlib",
 "xml",
 "xml.dom",
 "xml.etree",
 "xml.parsers",
 "xml.sax",
 "xmlrpc",
-"zoneinfo",
-
-// Extension modules built by setup.py
-"_asyncio",
-"_bisect",
-"_blake2",
-"_bz2",
-"_codecs_cn",
-"_codecs_hk",
-"_codecs_iso2022",
-"_codecs_jp",
-"_codecs_kr",
-"_codecs_tw",
-"_contextvars",
-"_crypt",
-"_csv",
-"_ctypes",
-"_curses",
-"_curses_panel",
-"_datetime",
-"_dbm",
-"_decimal",
-"_elementtree",
-"_gdbm",
-"_hashlib",
-"_heapq",
-"_json",
-"_lsprof",
-"_lzma",
-"_md5",
-"_multibytecodec",
-"_multiprocessing",
-"_opcode",
-"_pickle",
-"_posixshmem",
-"_posixsubprocess",
-"_queue",
-"_random",
-"_sha1",
-"_sha256",
-"_sha3",
-"_sha512",
-"_socket",
-"_sqlite3",
-"_ssl",
-"_statistics",
-"_struct",
-"_tkinter",
-"_uuid",
-"_xxsubinterpreters",
-"_zoneinfo",
-"array",
-"audioop",
-"binascii",
-"cmath",
-"fcntl",
-"grp",
-"math",
-"mmap",
-"nis",
-"ossaudiodev",
-"pyexpat",
-"readline",
-"resource",
-"select",
-"spwd",
-"syslog",
-"termios",
-"unicodedata",
+"zipapp",
+"zipfile",
+"zipimport",
 "zlib",
-
-// Built-in and extension modules built by Modules/Setup
-"_abc",
-"_codecs",
-"_collections",
-"_functools",
-"_io",
-"_locale",
-"_operator",
-"_signal",
-"_sre",
-"_stat",
-"_symtable",
-"_thread",
-"_tracemalloc",
-"_weakref",
-"atexit",
-"errno",
-"faulthandler",
-"itertools",
-"posix",
-"pwd",
-"time",
-
-// Windows extension modules
-"_msi",
-"_winapi",
-"msvcrt",
-"nt",
-"winreg",
-"winsound",
-
+"zoneinfo",
 };
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index e9df8fb5d273a..a97f45d0d5dc4 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -18,8 +18,6 @@
 #include "pycore_sysmodule.h"     // _PySys_ClearAuditHooks()
 #include "pycore_traceback.h"     // _Py_DumpTracebackThreads()
 
-#include "module_names.h"         // _Py_module_names
-
 #include <locale.h>               // setlocale()
 
 #ifdef HAVE_SIGNAL_H
@@ -2499,7 +2497,7 @@ fatal_error_exit(int status)
 
 
 // Dump the list of extension modules of sys.modules, excluding stdlib modules
-// (_Py_module_names), into fd file descriptor.
+// (sys.module_names), into fd file descriptor.
 //
 // This function is called by a signal handler in faulthandler: avoid memory
 // allocations and keep the implementation simple. For example, the list is not
@@ -2515,10 +2513,31 @@ _Py_DumpExtensionModules(int fd, PyInterpreterState *interp)
         return;
     }
 
+    Py_ssize_t pos;
+    PyObject *key, *value;
+
+    // Avoid PyDict_GetItemString() which calls PyUnicode_FromString(),
+    // memory cannot be allocated on the heap in a signal handler.
+    // Iterate on the dict instead.
+    PyObject *module_names = NULL;
+    pos = 0;
+    while (PyDict_Next(interp->sysdict, &pos, &key, &value)) {
+        if (PyUnicode_Check(key)
+           && PyUnicode_CompareWithASCIIString(key, "module_names") == 0) {
+            module_names = value;
+            break;
+        }
+    }
+    // If we failed to get sys.module_names or it's not a frozenset,
+    // don't exclude stdlib modules.
+    if (module_names != NULL && !PyFrozenSet_Check(module_names)) {
+        module_names = NULL;
+    }
+
+    // List extensions
     int header = 1;
     Py_ssize_t count = 0;
-    Py_ssize_t pos = 0;
-    PyObject *key, *value;
+    pos = 0;
     while (PyDict_Next(modules, &pos, &key, &value)) {
         if (!PyUnicode_Check(key)) {
             continue;
@@ -2526,22 +2545,26 @@ _Py_DumpExtensionModules(int fd, PyInterpreterState *interp)
         if (!_PyModule_IsExtension(value)) {
             continue;
         }
-
-        // Check if it is a stdlib extension module.
         // Use the module name from the sys.modules key,
         // don't attempt to get the module object name.
-        const Py_ssize_t names_len = Py_ARRAY_LENGTH(_Py_module_names);
-        int is_stdlib_mod = 0;
-        for (Py_ssize_t i=0; i < names_len; i++) {
-            const char *name = _Py_module_names[i];
-            if (PyUnicode_CompareWithASCIIString(key, name) == 0) {
-                is_stdlib_mod = 1;
-                break;
+        if (module_names != NULL) {
+            int is_stdlib_ext = 0;
+
+            Py_ssize_t i;
+            PyObject *item;
+            Py_hash_t hash;
+            for (i=0; _PySet_NextEntry(module_names, &i, &item, &hash); ) {
+                if (PyUnicode_Check(item)
+                    && PyUnicode_Compare(key, item) == 0)
+                {
+                    is_stdlib_ext = 1;
+                    break;
+                }
+            }
+            if (is_stdlib_ext) {
+                // Ignore stdlib extension
+                continue;
             }
-        }
-        if (is_stdlib_mod) {
-            // Ignore stdlib extension module.
-            continue;
         }
 
         if (header) {
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index 720532eade29b..e2f7e39f33329 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -29,6 +29,7 @@ Data members:
 #include "frameobject.h"          // PyFrame_GetBack()
 #include "pydtrace.h"
 #include "osdefs.h"               // DELIM
+#include "module_names.h"         // _Py_module_names
 #include <locale.h>
 
 #ifdef MS_WINDOWS
@@ -2020,33 +2021,63 @@ static PyMethodDef sys_methods[] = {
     {NULL,              NULL}           /* sentinel */
 };
 
+
 static PyObject *
 list_builtin_module_names(void)
 {
     PyObject *list = PyList_New(0);
-    int i;
-    if (list == NULL)
+    if (list == NULL) {
         return NULL;
-    for (i = 0; PyImport_Inittab[i].name != NULL; i++) {
-        PyObject *name = PyUnicode_FromString(
-            PyImport_Inittab[i].name);
-        if (name == NULL)
-            break;
-        PyList_Append(list, name);
+    }
+    for (Py_ssize_t i = 0; PyImport_Inittab[i].name != NULL; i++) {
+        PyObject *name = PyUnicode_FromString(PyImport_Inittab[i].name);
+        if (name == NULL) {
+            goto error;
+        }
+        if (PyList_Append(list, name) < 0) {
+            Py_DECREF(name);
+            goto error;
+        }
         Py_DECREF(name);
     }
     if (PyList_Sort(list) != 0) {
-        Py_DECREF(list);
-        list = NULL;
+        goto error;
+    }
+    PyObject *tuple = PyList_AsTuple(list);
+    Py_DECREF(list);
+    return tuple;
+
+error:
+    Py_DECREF(list);
+    return NULL;
+}
+
+
+static PyObject *
+list_module_names(void)
+{
+    Py_ssize_t len = Py_ARRAY_LENGTH(_Py_module_names);
+    PyObject *names = PyTuple_New(len);
+    if (names == NULL) {
+        return NULL;
     }
-    if (list) {
-        PyObject *v = PyList_AsTuple(list);
-        Py_DECREF(list);
-        list = v;
+
+    for (Py_ssize_t i = 0; i < len; i++) {
+        PyObject *name = PyUnicode_FromString(_Py_module_names[i]);
+        if (name == NULL) {
+            Py_DECREF(names);
+            return NULL;
+        }
+        PyTuple_SET_ITEM(names, i, name);
     }
-    return list;
+
+    PyObject *set = PyObject_CallFunction((PyObject *)&PyFrozenSet_Type,
+                                          "(O)", names);
+    Py_DECREF(names);
+    return set;
 }
 
+
 /* Pre-initialization support for sys.warnoptions and sys._xoptions
  *
  * Modern internal code paths:
@@ -2753,6 +2784,7 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict)
     SET_SYS("hash_info", get_hash_info(tstate));
     SET_SYS("maxunicode", PyLong_FromLong(0x10FFFF));
     SET_SYS("builtin_module_names", list_builtin_module_names());
+    SET_SYS("module_names", list_module_names());
 #if PY_BIG_ENDIAN
     SET_SYS_FROM_STRING("byteorder", "big");
 #else
diff --git a/Tools/scripts/generate_module_names.py b/Tools/scripts/generate_module_names.py
index 985a1a5e5a2d9..9d363aa04b300 100644
--- a/Tools/scripts/generate_module_names.py
+++ b/Tools/scripts/generate_module_names.py
@@ -17,27 +17,6 @@
     '__pycache__',
     'site-packages',
 
-    # Helper modules of public modules.
-    # For example, sysconfig uses _osx_support.
-    '_aix_support',
-    '_collections_abc',
-    '_compat_pickle',
-    '_compression',
-    '_markupbase',
-    '_osx_support',
-    '_sitebuiltins',
-    '_strptime',
-    '_threading_local',
-    '_weakrefset',
-
-    # Used to bootstrap setup.py
-    '_bootsubprocess',
-
-    # pure Python implementation
-    '_py_abc',
-    '_pydecimal',
-    '_pyio',
-
     # test modules
     '__phello__.foo',
     '_ctypes_test',
@@ -69,40 +48,20 @@
 )
 
 
-def write_comment(fp, comment):
-    print(f"// {comment}", file=fp)
-
-
-def write_modules(fp, names):
-    for name in sorted(names):
-        if name in IGNORE:
-            continue
-        print(f'"{name}",', file=fp)
-    print(file=fp)
-
-
-def list_builtin_modules(fp):
-    write_comment(fp, "Built-in modules")
-    write_modules(fp, sys.builtin_module_names)
-
-
 # Pure Python modules (Lib/*.py)
-def list_python_modules(fp):
-    write_comment(fp, "Pure Python modules (Lib/*.py)")
-    names = []
+def list_python_modules(names):
     for filename in os.listdir(STDLIB_PATH):
         if not filename.endswith(".py"):
             continue
         name = filename.removesuffix(".py")
-        names.append(name)
-    write_modules(fp, names)
+        names.add(name)
 
 
 def _list_sub_packages(path, names, parent=None):
     for name in os.listdir(path):
-        package_path = os.path.join(path, name)
         if name in IGNORE:
             continue
+        package_path = os.path.join(path, name)
         if not os.path.isdir(package_path):
             continue
         if not any(package_file.endswith(".py")
@@ -114,40 +73,28 @@ def _list_sub_packages(path, names, parent=None):
             qualname = name
         if qualname in IGNORE:
             continue
-        names.append(qualname)
+        names.add(qualname)
         _list_sub_packages(package_path, names, qualname)
 
 
 # Packages and sub-packages
-def list_packages(fp):
-    write_comment(fp, "Packages and sub-packages")
-    names = []
+def list_packages(names):
     _list_sub_packages(STDLIB_PATH, names)
-    write_modules(fp, names)
-
-
-# Windows extensions
-def list_windows_extensions(fp):
-    write_comment(fp, "Windows extension modules")
-    write_modules(fp, WINDOWS_MODULES)
 
 
 # Extension modules built by setup.py
-def list_setup(fp):
+def list_setup_extensions(names):
     cmd = [sys.executable, SETUP_PY, "-q", "build", "--list-module-names"]
     output = subprocess.check_output(cmd)
     output = output.decode("utf8")
-    names = output.splitlines()
-
-    write_comment(fp, "Extension modules built by setup.py")
-    write_modules(fp, names)
+    extensions = output.splitlines()
+    names |= set(extensions)
 
 
 # Built-in and extension modules built by Modules/Setup
-def list_modules_setup(fp):
+def list_modules_setup_extensions(names):
     assign_var = re.compile("^[A-Z]+=")
 
-    names = []
     with open(MODULES_SETUP, encoding="utf-8") as modules_fp:
         for line in modules_fp:
             # Strip comment
@@ -165,25 +112,26 @@ def list_modules_setup(fp):
                 continue
             # "errno errnomodule.c" => write "errno"
             name = parts[0]
-            names.append(name)
+            names.add(name)
 
-    write_comment(fp, "Built-in and extension modules built by Modules/Setup")
-    write_modules(fp, names)
+
+def list_modules():
+    names = set(sys.builtin_module_names) | set(WINDOWS_MODULES)
+    list_modules_setup_extensions(names)
+    list_setup_extensions(names)
+    list_packages(names)
+    list_python_modules(names)
+    names -= set(IGNORE)
+    return names
 
 
-def list_modules(fp):
+def write_modules(fp, names):
     print("// Auto-generated by Tools/scripts/generate_module_names.py.", file=fp)
+    print("// List used to create sys.module_names.", file=fp)
     print(file=fp)
     print("static const char* _Py_module_names[] = {", file=fp)
-    print(file=fp)
-
-    list_builtin_modules(fp)
-    list_python_modules(fp)
-    list_packages(fp)
-    list_setup(fp)
-    list_modules_setup(fp)
-    list_windows_extensions(fp)
-
+    for name in sorted(names):
+        print(f'"{name}",', file=fp)
     print("};", file=fp)
 
 
@@ -193,7 +141,9 @@ def main():
               file=sys.stderr)
         sys.exit(1)
 
-    list_modules(sys.stdout)
+    fp = sys.stdout
+    names = list_modules()
+    write_modules(fp, names)
 
 
 if __name__ == "__main__":



More information about the Python-checkins mailing list