https://github.com/python/cpython/commit/58567cc18c5b048e08307b5ba18a9934a395ca42 commit: 58567cc18c5b048e08307b5ba18a9934a395ca42 branch: main author: Jelle Zijlstra <jelle.zijls...@gmail.com> committer: JelleZijlstra <jelle.zijls...@gmail.com> date: 2025-04-28T08:38:56-07:00 summary:
gh-132952: Speed up startup by importing _io instead of io (#132957) files: A Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-08-49-05.gh-issue-132952.ifvP10.rst M Include/internal/pycore_global_objects_fini_generated.h M Include/internal/pycore_global_strings.h M Include/internal/pycore_runtime_init_generated.h M Include/internal/pycore_unicodeobject_generated.h M Lib/io.py M Lib/site.py M Lib/test/test_io.py M Lib/test/test_site.py M Modules/_io/_iomodule.c M Python/pylifecycle.c diff --git a/Include/internal/pycore_global_objects_fini_generated.h b/Include/internal/pycore_global_objects_fini_generated.h index 5485d0bd64f3f1..e412db1de68f8b 100644 --- a/Include/internal/pycore_global_objects_fini_generated.h +++ b/Include/internal/pycore_global_objects_fini_generated.h @@ -1023,6 +1023,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) { _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intern)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intersection)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(interval)); + _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(io)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_running)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_struct)); _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isatty)); diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index 3ce192511e3879..2a6c2065af6bb9 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -514,6 +514,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(intern) STRUCT_FOR_ID(intersection) STRUCT_FOR_ID(interval) + STRUCT_FOR_ID(io) STRUCT_FOR_ID(is_running) STRUCT_FOR_ID(is_struct) STRUCT_FOR_ID(isatty) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 5c95d0feddecba..2368157a4fd18b 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1021,6 +1021,7 @@ extern "C" { INIT_ID(intern), \ INIT_ID(intersection), \ INIT_ID(interval), \ + INIT_ID(io), \ INIT_ID(is_running), \ INIT_ID(is_struct), \ INIT_ID(isatty), \ diff --git a/Include/internal/pycore_unicodeobject_generated.h b/Include/internal/pycore_unicodeobject_generated.h index a1fc9736d66618..72c3346328a552 100644 --- a/Include/internal/pycore_unicodeobject_generated.h +++ b/Include/internal/pycore_unicodeobject_generated.h @@ -1844,6 +1844,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) { _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); assert(PyUnicode_GET_LENGTH(string) != 1); + string = &_Py_ID(io); + _PyUnicode_InternStatic(interp, &string); + assert(_PyUnicode_CheckConsistency(string, 1)); + assert(PyUnicode_GET_LENGTH(string) != 1); string = &_Py_ID(is_running); _PyUnicode_InternStatic(interp, &string); assert(_PyUnicode_CheckConsistency(string, 1)); diff --git a/Lib/io.py b/Lib/io.py index e9fe619392e3d9..63ffadb1d385f9 100644 --- a/Lib/io.py +++ b/Lib/io.py @@ -60,9 +60,6 @@ IncrementalNewlineDecoder, text_encoding, TextIOWrapper) -# Pretend this exception was created here. -UnsupportedOperation.__module__ = "io" - # for seek() SEEK_SET = 0 SEEK_CUR = 1 diff --git a/Lib/site.py b/Lib/site.py index 9da8b6724e1cec..5c38b1b17d5abd 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -73,7 +73,7 @@ import os import builtins import _sitebuiltins -import io +import _io as io import stat # Prefixes for site-packages; add additional prefixes like /usr/local here diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index ac3b6d131e7dad..545643aa455a5b 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -445,6 +445,25 @@ def test_invalid_operations(self): self.assertRaises(exc, fp.seek, 1, self.SEEK_CUR) self.assertRaises(exc, fp.seek, -1, self.SEEK_END) + @support.cpython_only + def test_startup_optimization(self): + # gh-132952: Test that `io` is not imported at startup and that the + # __module__ of UnsupportedOperation is set to "io". + assert_python_ok("-S", "-c", textwrap.dedent( + """ + import sys + assert "io" not in sys.modules + try: + sys.stdin.truncate() + except Exception as e: + typ = type(e) + assert typ.__module__ == "io", (typ, typ.__module__) + assert typ.__name__ == "UnsupportedOperation", (typ, typ.__name__) + else: + raise AssertionError("Expected UnsupportedOperation") + """ + )) + @unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()") def test_optional_abilities(self): # Test for OSError when optional APIs are not supported diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 16cf25798a73a3..a7e9241f44d243 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -8,6 +8,7 @@ import test.support from test import support from test.support.script_helper import assert_python_ok +from test.support import import_helper from test.support import os_helper from test.support import socket_helper from test.support import captured_stderr @@ -574,6 +575,17 @@ def test_license_exists_at_url(self): code = e.code self.assertEqual(code, 200, msg="Can't find " + url) + @support.cpython_only + def test_lazy_imports(self): + import_helper.ensure_lazy_imports("site", [ + "io", + "locale", + "traceback", + "atexit", + "warnings", + "textwrap", + ]) + class StartupImportTests(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-08-49-05.gh-issue-132952.ifvP10.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-08-49-05.gh-issue-132952.ifvP10.rst new file mode 100644 index 00000000000000..2792ce35c159df --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-26-08-49-05.gh-issue-132952.ifvP10.rst @@ -0,0 +1,4 @@ +Speed up startup with the ``-S`` argument by importing the +private ``_io`` module instead of :mod:`io`. This fixes a performance +regression introduced earlier in Python 3.14 development and restores performance +to the level of Python 3.13. diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c index bd4d994454dfcd..50fe5d50c91a35 100644 --- a/Modules/_io/_iomodule.c +++ b/Modules/_io/_iomodule.c @@ -661,6 +661,11 @@ iomodule_exec(PyObject *m) "UnsupportedOperation", PyExc_OSError, PyExc_ValueError); if (state->unsupported_operation == NULL) return -1; + if (PyObject_SetAttrString(state->unsupported_operation, + "__module__", &_Py_ID(io)) < 0) + { + return -1; + } if (PyModule_AddObjectRef(m, "UnsupportedOperation", state->unsupported_operation) < 0) { diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 1b9832bff17ba5..0871e147169328 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -2755,7 +2755,7 @@ init_set_builtins_open(void) goto error; } - if (!(wrapper = PyImport_ImportModuleAttrString("io", "open"))) { + if (!(wrapper = PyImport_ImportModuleAttrString("_io", "open"))) { goto error; } @@ -2800,7 +2800,7 @@ init_sys_streams(PyThreadState *tstate) } #endif - if (!(iomod = PyImport_ImportModule("io"))) { + if (!(iomod = PyImport_ImportModule("_io"))) { goto error; } _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com