https://github.com/python/cpython/commit/58567cc18c5b048e08307b5ba18a9934a395ca42
commit: 58567cc18c5b048e08307b5ba18a9934a395ca42
branch: main
author: Jelle Zijlstra <[email protected]>
committer: JelleZijlstra <[email protected]>
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 -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]