2026, Jan 09 03:02
Почему перенаправленный stdout в Windows уходит в ANSI и как получить UTF‑8 в Python
Почему при перенаправлении stdout в Windows Python даёт ANSI вместо UTF‑8 и как это исправить: -X utf8, PYTHONUTF8, PYTHONIOENCODING, encoding=utf-8 в CI.
Почему строка Unicode, которая без проблем декодируется в Python, оказывается в кодировке ANSI в командной строке Windows при перенаправлении вывода в файл? Симптом сбивает с толку: байты раскодируются в правильный символ, но Notepad++ сообщает, что файл в ANSI, и вне западных локалей глиф может отображаться неверно. Давайте разберёмся, что на самом деле происходит со stdout в Windows и как добиться стабильного вывода в UTF‑8.
Как воспроизвести проблему
Ниже последовательность, которая декодирует UTF‑8‑байты символа Ö и перенаправляет вывод в файл:
>python --version
Python 3.13.0
>python -c "print(b'\xc3\x96'.decode('utf-8'))" > test.txt
Открыв test.txt в Notepad++, вы увидите, что кодировка определена как ANSI. Запуск аналогичной команды в MSYS2 (Python 3.11.6) даёт файл, закодированный в UTF‑8. Разница связана с окружением, а не с процессом декодирования.
Что происходит на самом деле
Ключевой момент: decode возвращает строку Unicode. На этом этапе файловая кодировка вообще ни при чём. Следующие две однострочные команды по сути эквивалентны:
python -c "print(b'\xc3\x96'.decode('utf-8'))" > test.txt
python -c "print('Ö')" > test.txt
Кодировка вступает в дело только тогда, когда print записывает в stdout. В Windows, если stdout перенаправлён в файл, ОС использует стандартную «ANSI»‑страницу кодировки, заданную локализацией установленной системы. Для США и Западной Европы это, как правило, Windows‑1252. Поэтому итоговый файл получается в ANSI, а не в UTF‑8. В других окружениях, например в MSYS2, stdout настроен иначе и может по умолчанию быть UTF‑8 — отсюда другой результат.
Как получить вывод в UTF‑8
Включите в Python режим UTF‑8, чтобы stdout использовал UTF‑8 даже при перенаправлении. Его можно активировать для конкретного процесса с ключом -X utf8:
python -X utf8 -c "print('Ö')" > test.txt
Либо установите переменную окружения PYTHONUTF8=1, чтобы включить режим UTF‑8 для Python. Если нужно точечно управлять именно перенаправляемыми stdin/stdout/stderr, используйте PYTHONIOENCODING — она позволяет явно задать кодировку для этих потоков.
Ещё один надёжный путь — не полагаться на перенаправление ОС, а писать в файловый объект с заданной вами кодировкой. Тогда print будет работать с потоком, открытым в UTF‑8:
data_bytes = b'\xc3\x96'
text_unicode = data_bytes.decode('utf-8')
with open('test.txt', 'w', encoding='utf-8') as out_file:
print(text_unicode, file=out_file)
Такой способ создаёт файл в UTF‑8 независимо от поведения консоли и перенаправления в ОС, потому что кодировка явно задаётся на стороне Python.
Почему это важно
Конвейеры в консоли и журналы CI часто перенаправляют stdout в файлы. Если кодировка незаметно откатывается к ANSI‑странице, не‑ASCII‑символы могут повредиться или неверно интерпретироваться дальше по цепочке. Предсказуемый вывод в UTF‑8 предотвращает такие несоответствия в разных оболочках и средах. Если вы упаковываете код в исполняемый файл и вам нужна единообразная работа, можно встроить переключатель режима UTF‑8, добавив параметр 'X utf8' в файл .spec.
Выводы
Декодирование байтов в Unicode в Python работает как и должно; неожиданность возникает позже — на этапе кодирования stdout. В Windows перенаправлённый stdout по умолчанию использует локальную «ANSI»‑кодировку, в западных регионах это обычно Windows‑1252. Если вам важны именно записанные байты, задайте кодировку явно: включите режим UTF‑8 через -X utf8 или PYTHONUTF8, воспользуйтесь PYTHONIOENCODING для перенаправляемых потоков или выводите в файл, открытый с encoding='utf-8'. Так вы сохраните переносимость и целостность текста.