为什么这段代码在Windows和Linux之间打印出不同的结果?

7

这段代码在Windows和Linux之间输出不同的字符串。

test.py:

print(";".join([str(i) for i in range(10000)]))

平台:x86_64 Linux 4.4.0-17763-Microsoft
Python版本:3.7.2
终端:bash,fish

简要输出:

$ python --version
Python 3.7.2
$ python test.py
0;1;2;3;4;5;6....9997;9998;9999
$ python -u test.py
0;1;2;3;4;5;6....9997;9998;9999

平台:Windows 10 1809
Python 版本:3.6.8、3.7.0、3.7.2
终端:cmd、powershell

简略输出:

./python --version
Python 3.6.8
./python test.py
0;1;2;3;4;5;6....9997;9998;9999
./python -u test.py
0;1;2;3;4;5;6....2663;2664;2665;26

./python --version
Python 3.7.0
./python test.py
0;1;2;3;4;5;6....9997;9998;9999
./python -u test.py
0;1;2;3;4;5;6....2663;2664;2665;26

./python --version
Python 3.7.2
./python test.py
0;1;2;3;4;5;6....9997;9998;9999
./python -u test.py
0;1;2;3;4;5;6....2663;2664;2665;26

因此,为什么在Windows中使用-u参数会导致输出被截断(只显示从02666)?
(当使用python -u test.py > a.txt将输出重定向到文件时,它可以正常工作。)
也许是缓冲的原因?

4
我可以确认这很奇怪。“-u”会强制stdout/stderr的二进制I/O层无缓冲 - stdin始终有缓冲;文本I/O层将采用行缓冲,这适用于您的输出 - 因此最大行大小似乎为12222(这是您的输出长度...) - 在Linux中似乎有更大的余地... - Patrick Artner
谢谢您的回复。我还有一些关于这个问题的疑问。maxline 是什么意思?是 Windows 的一个常量吗?当没有缓冲区时,stdout 实际上是做什么的?我在哪里可以找到更多相关信息? - OhYee
maxline只是我编造的一个符号,用来表示在Windows控制台下似乎(在-u模式下)在12222个字符处停止向标准输出写入-尽管我正在寻找,但无法确认任何“硬”限制-谷歌也没有帮助。也许一些Windows高手可以解决这个问题... - Patrick Artner
好的,谢谢您的回复。 - OhYee
@eryksun,感谢您回答OhYee的发现-如果您想把它变成真正的答案,请与我联系,我会给它点赞。希望很快它就会成为历史事实,超越那些还在使用Win7(除了一些陷入中世纪的客户);D - Patrick Artner
1个回答

2
使用WINAPI的WriteFileWriteConsoleW函数输出控制台信息时,它们的最大容量限制是模糊定义的。具体如下:

nNumberOfCharsToWrite [输入参数]
要写入的字符数目。如果指定的字符总大小超过了可用的堆大小,则函数将以ERROR_NOT_ENOUGH_MEMORY错误失败。

这段话提到的“heap”是指哪个堆并没有被记录。一个进程可以有多个大小不同(固定或动态)的堆。NT运行时库中的本地堆实现(例如RtlCreateHeap)可以在指定地址创建一个堆,从而方便地访问与其他进程共享的内存。使用共享堆通常与本地进程间通信(LPC)端口结合使用 -- 或者在NT 6.0+中使用异步LPC。LPC端口用于在应用程序和系统服务之间传递消息,例如会话管理器(smss.exe)、服务控制管理器(services.exe)、本地安全性权限提供程序(lsass.exe)、桌面会话服务器(csrss.exe)以及控制台主机的实例(conhost.exe)。直接排队到LPC端口的消息限制为256字节。较大的消息通过将引用共享内存的消息排队到端口来传递。
原文描述的是旧版控制台实现(NT 6.3之前)使用LPC作为I/O通道,并且上述堆大小仅为64 KiB。这是一种奇特的设计选择,我认为某些人过于迷恋用户模式子系统、消息传递等技术。在正确的NT I/O中,使用带有I/O系统服务的设备,包括NtCreateFile、NtReadFile、NtWriteFile和NtDeviceIoControlFile。
控制台应用程序不知道可用于写入的堆的大小。Python 可以从 64 KiB 开始并逐步减少,但其 原始文件 I/O 每次调用需要一个系统调用。因此,它将写入限制在 32 KiB,这应该会成功。此限制允许使用最多 16K 个 UTF-16 代码点编写宽字符字符串。一个复杂性是控制台 I/O 堆栈在 3.6+ 中使用 UTF-8,必须通过 MultiByteToWideChar 进行解码。目前,它只是反复将 UTF-8 缓冲区分成两半,直到结果长度小于 16K。因此,在问题的示例中,写入 48,889 个字符被减半为 24,444 个字符,再减半为 12,222 个字符。(依我看,更好的做法是尝试写入最多 16K 个代码点;获取实际写入的数量,并对子串调用 WideCharToMultiByte 来确定写入的 UTF-8 字节数。如果 UTF-8 的 2-4 字节序列与切割点重叠,则当前设计实际上存在错误。)
在NT 6.3+(Windows 8.1+)中,控制台I/O没有这个大小限制,因为它使用ConDrv设备和I/O系统调用而不是LPC。然而,特定代码只为了支持无缓冲文本I/O堆栈并不值得,如-u命令行选项所配置的那样。我们期望交互式控制台I/O被缓冲。实际上,使用常规open调用会禁止无缓冲文本I/O。例如:
>>> open('conout$', 'w', buffering=0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: can't have unbuffered text I/O

Windows 7的扩展支持将于2020年1月14日结束,因此Python 3.8将是最后一个支持它的版本。控制台写入限制应在Python 3.9中被移除。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接