我试图修复的脚本使用以下范例将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
写入一个仅用于控制台I / O的sys.stdout
对象。具体来说,在3.6+中,当stdout最初是一个控制台文件1时,Python 3的标准输出文件(即sys.stdout.buffer.raw
)的原始层是一个io._WindowsConsoleIO
实例。此对象缓存stdout文件descriptor2的初始句柄值。随后,dup2
关闭此句柄,同时将文件描述符与“temp.out”的重复句柄重新关联。此时缓存的句柄不再有效。 (实际上,它不应该缓存句柄,因为与控制台I / O的成本相比,调用_get_osfhandle
相对便宜。)但是,即使它具有“temp.out”的有效句柄,sys.stdout.write
仍然会失败,因为_WindowsConsoleIO
使用仅控制台功能WriteConsoleW
而不是通用WriteFile
。
您需要重新分配sys.stdout
,而不是使用dup2
等低级操作绕过Python的I / O堆栈。从Unix开发人员的角度来看,我知道它并不理想。我希望我们可以重新实现Windows控制台支持Unicode的方式,而不会引入仅限控制台的_WindowsConsoleIO
类,这会破坏人们依赖数十年的低级模式。
1.添加了_WindowsConsoleIO
以支持Windows控制台中的所有Unicode(至少与控制台可以支持它一样)。为此,它使用控制台的UTF-16宽字符API(例如ReadConsoleW
和WriteConsoleW
)。以前,CPython的控制台支持仅限于使用Windows代码页编码的文本,使用通用的基于字节的I / O(例如ReadFile
和WriteFile
)。
2. Windows使用句柄来引用内核对象,例如File对象。该系统与POSIX文件描述符(FD)的行为不兼容。因此,C运行时(CRT)具有“低I / O”兼容层,它将POSIX样式的FD与Windows文件句柄相关联,并且还实现了POSIX I / O功能,如open
和write
。 CRT的_open_osfhandle
函数将本机文件句柄与FD关联,_get_osfhandle
返回与FD关联的句柄。有时CPython使用CRT低I / O层,有时它直接使用Windows API。如果你问我,这真是一团糟。