https://github.com/python/cpython/commit/92db1519aa089de78da70535ae6e4680050c0b5f
commit: 92db1519aa089de78da70535ae6e4680050c0b5f
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-18T20:03:25+03:00
summary:
gh-86427: Determine the stdio encoding for every standard stream on Windows
(GH-155416)
It was the ANSI code page instead of the encoding of the device the stream
is connected to, as in 3.7.
The stdio encoding is now left undefined in this mode and determined for
every standard stream.
files:
A Misc/NEWS.d/next/Windows/2026-08-09-05-30-00.gh-issue-86427.legacystdio.rst
D Misc/NEWS.d/next/Windows/2026-08-09-07-00-00.gh-issue-86427.consolecp.rst
M Lib/test/test_cmd_line.py
M Objects/unicodeobject.c
M Python/initconfig.c
M Python/pylifecycle.c
diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py
index 2200770b4a45f08..4c1abb15c0cb148 100644
--- a/Lib/test/test_cmd_line.py
+++ b/Lib/test/test_cmd_line.py
@@ -1069,8 +1069,9 @@ def test_python_legacy_windows_stdio(self):
@unittest.skipUnless(support.MS_WINDOWS, 'Test only applicable on Windows')
def test_python_legacy_windows_stdio_encoding(self):
- # gh-86427: In the legacy mode the encoding of the standard streams
- # is the encoding of the console.
+ # gh-86427: In the legacy mode the encoding of a standard stream is
+ # the encoding of the console it is connected to, which can differ
+ # for input and output.
import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
try:
@@ -1080,7 +1081,7 @@ def test_python_legacy_windows_stdio_encoding(self):
# We cannot use PIPE, because the standard streams should be
# connected to the console. So we use the exit code.
code = ("import sys; sys.exit(sys.stdin.encoding != 'cp850' or "
- "sys.stdout.encoding != 'cp850')")
+ "sys.stdout.encoding != 'cp437')")
env = os.environ.copy()
env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
env['PYTHONUTF8'] = '0'
@@ -1091,7 +1092,7 @@ def test_python_legacy_windows_stdio_encoding(self):
try:
if not kernel32.SetConsoleCP(850):
self.skipTest('cannot set the console input code page')
- if not kernel32.SetConsoleOutputCP(850):
+ if not kernel32.SetConsoleOutputCP(437):
self.skipTest('cannot set the console output code page')
proc = subprocess.run([sys.executable, '-c', code], env=env,
stdin=fin, stdout=fout,
diff --git
a/Misc/NEWS.d/next/Windows/2026-08-09-05-30-00.gh-issue-86427.legacystdio.rst
b/Misc/NEWS.d/next/Windows/2026-08-09-05-30-00.gh-issue-86427.legacystdio.rst
new file mode 100644
index 000000000000000..c3e2332defabfa9
--- /dev/null
+++
b/Misc/NEWS.d/next/Windows/2026-08-09-05-30-00.gh-issue-86427.legacystdio.rst
@@ -0,0 +1,3 @@
+Fix the encoding of the standard streams in the legacy Windows stdio mode
+(:envvar:`PYTHONLEGACYWINDOWSSTDIO`). It is now the encoding of the device
+the stream is connected to, as in Python 3.7, not the ANSI code page.
diff --git
a/Misc/NEWS.d/next/Windows/2026-08-09-07-00-00.gh-issue-86427.consolecp.rst
b/Misc/NEWS.d/next/Windows/2026-08-09-07-00-00.gh-issue-86427.consolecp.rst
deleted file mode 100644
index 1cbf188c12e4070..000000000000000
--- a/Misc/NEWS.d/next/Windows/2026-08-09-07-00-00.gh-issue-86427.consolecp.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Fix the encoding of the standard streams in the legacy Windows stdio mode
-(:envvar:`PYTHONLEGACYWINDOWSSTDIO`). It is now the code page of the
-console, as in Python 3.7, not the ANSI code page.
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
index 7ee9d17b7f77ff2..d2faa28722b0e03 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -15229,6 +15229,10 @@ init_stdio_encoding(PyInterpreterState *interp)
{
/* Update the stdio encoding to the normalized Python codec name. */
PyConfig *config = (PyConfig*)_PyInterpreterState_GetConfig(interp);
+ if (config->stdio_encoding == NULL) {
+ /* gh-86427: The encoding is determined for every stream. */
+ return _PyStatus_OK();
+ }
if (config_get_codec_name(&config->stdio_encoding) < 0) {
return _PyStatus_ERR("failed to get the Python codec name "
"of the stdio encoding");
diff --git a/Python/initconfig.c b/Python/initconfig.c
index 69d47a5872f5e38..49f1beb37bb9208 100644
--- a/Python/initconfig.c
+++ b/Python/initconfig.c
@@ -196,7 +196,7 @@ static const PyConfigSpec PYCONFIG_SPEC[] = {
SPEC(show_ref_count, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL),
SPEC(site_import, BOOL, READ_ONLY, NO_SYS, GLOBAL(&Py_NoSiteFlag, 1)), //
sys.flags.no_site
SPEC(skip_source_first_line, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL),
- SPEC(stdio_encoding, WSTR, READ_ONLY, NO_SYS, NO_GLOBAL),
+ SPEC(stdio_encoding, WSTR_OPT, READ_ONLY, NO_SYS, NO_GLOBAL),
SPEC(stdio_errors, WSTR, READ_ONLY, NO_SYS, NO_GLOBAL),
SPEC(tracemalloc, UINT, READ_ONLY, NO_SYS, NO_GLOBAL),
SPEC(use_frozen_modules, BOOL, READ_ONLY, NO_SYS, NO_GLOBAL),
@@ -1074,11 +1074,14 @@ config_check_consistency(const PyConfig *config)
assert(config->module_search_paths_set >= 0);
assert(config->filesystem_encoding != NULL);
assert(config->filesystem_errors != NULL);
- assert(config->stdio_encoding != NULL);
- assert(config->stdio_errors != NULL);
#ifdef MS_WINDOWS
+ /* stdio_encoding can be NULL in the legacy Windows stdio mode. */
+ assert(config->stdio_encoding != NULL || config->legacy_windows_stdio);
assert(config->legacy_windows_stdio >= 0);
+#else
+ assert(config->stdio_encoding != NULL);
#endif
+ assert(config->stdio_errors != NULL);
/* -c and -m options are exclusive */
assert(!(config->run_command != NULL && config->run_module != NULL));
assert(config->check_hash_pycs_mode != NULL);
@@ -2709,31 +2712,15 @@ config_init_stdio_encoding(PyConfig *config,
}
/* Choose the default error handler based on the current locale. */
- if (config->stdio_encoding == NULL) {
+ if (config->stdio_encoding == NULL
#ifdef MS_WINDOWS
- /* gh-86427: use the console code page. Only one encoding can be
- specified, so the output code page is used: it affects two
- streams of three. */
- UINT cp = config->legacy_windows_stdio ? GetConsoleOutputCP() : 0;
- if (cp != 0) {
- if (cp == CP_UTF8) {
- status = PyConfig_SetString(config, &config->stdio_encoding,
- L"utf-8");
- }
- else {
- wchar_t encoding[20];
- swprintf(encoding, Py_ARRAY_LENGTH(encoding), L"cp%u",
- (unsigned int)cp);
- status = PyConfig_SetString(config, &config->stdio_encoding,
- encoding);
- }
- }
- else
+ /* gh-86427: it is determined for each stream: create_stdio() uses
+ _Py_device_encoding(), falling back to the locale encoding. */
+ && !config->legacy_windows_stdio
#endif
- {
- status = config_get_locale_encoding(config, preconfig,
- &config->stdio_encoding);
- }
+ ) {
+ status = config_get_locale_encoding(config, preconfig,
+ &config->stdio_encoding);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c
index 6074d4439531f42..a9c98d73fd0f721 100644
--- a/Python/pylifecycle.c
+++ b/Python/pylifecycle.c
@@ -3080,7 +3080,18 @@ create_stdio(const PyConfig *config, PyObject* io,
newline = "\n";
#endif
- PyObject *encoding_str = PyUnicode_FromWideChar(encoding, -1);
+ PyObject *encoding_str;
+ if (encoding != NULL) {
+ encoding_str = PyUnicode_FromWideChar(encoding, -1);
+ }
+ else {
+ /* gh-86427: use the encoding of the device. */
+ encoding_str = _Py_device_encoding(fd);
+ if (encoding_str == Py_None) {
+ Py_DECREF(encoding_str);
+ encoding_str = _Py_GetLocaleEncodingObject();
+ }
+ }
if (encoding_str == NULL) {
Py_CLEAR(buf);
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]