Сценарий, который я пытаюсь исправить, использует следующую парадигму для перенаправления stdout в файл.
import os
stdio_file = 'temp.out'
flag = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
stdio_fp = os.open(stdio_file, flag)
os.dup2(stdio_fp, 1)
print("hello")
На Python 2 это работает. На Python 3 вы получаете OSError
Traceback (most recent call last):
File "test.py", line 6, in <module>
print("hello")
OSError: [WinError 6] The handle is invalid
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>
OSError: [WinError 6] The handle is invalid
Я предполагаю, что есть более предпочтительные методы для маршрутизации stdout через файл, но мне интересно, почему этот метод перестает работать в Python 3 и если есть простой способ его исправить?
Код, например os.dup2(stdio_fp, 1)
будет работать в Python 3.5 и ранее, или в 3. 6+ с переменной среды PYTHONLEGACYWINDOWSSTDIO
.
Проблема заключается в том, что print
записывается в объект sys.stdout
который предназначен только для консольных sys.stdout
ввода-вывода. В частности, в 3. 6+ исходный слой стандартного выходного файла Python 3 (т.е. sys.stdout.buffer.raw
) является экземпляром io._WindowsConsoleIO
когда stdout изначально представляет собой файл консоли 1. Этот объект кэширует начальное значение дескриптора дескриптора файла stdout 2. Впоследствии dup2
закрывает этот дескриптор, повторно связав дескриптор файла с дублирующимся дескриптором для "temp.out". На этом этапе кешированная ручка больше не действует. (На самом деле, он не должен кэшировать дескриптор, так как вызов _get_osfhandle
относительно дешев по сравнению с затратами на консольный ввод-вывод.) Однако, даже если у него был действительный дескриптор для "temp.out", sys.stdout.write
так или иначе, поскольку _WindowsConsoleIO
использует только консольную функцию WriteConsoleW
вместо общего WriteFile
.
Вам нужно переназначить sys.stdout
вместо обхода стека ввода-вывода Python с помощью операций низкого уровня, таких как dup2
. Я знаю, что он не идеален с точки зрения разработчиков Unix. Хотелось бы, чтобы мы могли повторно реализовать способ поддержки Unicode для консоли Windows, не представляя этот консольный класс _WindowsConsoleIO
, что нарушает низкоуровневые шаблоны, на которые люди полагались десятилетиями.
1. _WindowsConsoleIO
был добавлен для поддержки всего диапазона Unicode в консоли Windows (по крайней мере, так же как консоль может его поддерживать).Для этого он использует ReadConsoleW
API-интерфейс консоли UTF-16 (например, ReadConsoleW
и WriteConsoleW
).Раньше поддержка консоли CPython ограничивалась текстом, который был закодирован с кодовыми страницами Windows, используя общие байтовые ReadFile
ввода-вывода (например, ReadFile
и WriteFile
).
2. Windows использует дескрипторы для ссылки на объекты ядра, такие как объекты File.Эта система несовместима с поведением с файловыми дескрипторами POSIX (FD).Таким образом, среда выполнения C (CRT) имеет уровень совместимости с "низким уровнем ввода-вывода", который связывает FD файлы в стиле POSIX с файлами Windows, а также реализует функции ввода-вывода POSIX, такие как open
и write
.Функция _open_osfhandle
связывает собственный дескриптор файла с FD, а _get_osfhandle
возвращает дескриптор, связанный с FD.Иногда CPython использует низкий уровень ввода-вывода CRT, и иногда он напрямую использует Windows API.Это действительно беспорядок, если вы спросите меня.
PYTHONLEGACYWINDOWSSTDIO
устраняет проблему. Тем не менее, я все еще пытаюсь проработать дополнительный комментарий в моей голове. Я вроде незнаком со многими вещами ОС.