【问题标题】:Why does this code print a different result between Windows and Linux?为什么这段代码在 Windows 和 Linux 之间打印出不同的结果?
【发布时间】:2019-06-13 10:35:40
【问题描述】:

此代码在 Windows 和 Linux 之间打印不同的字符串。

test.py:

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

平台:x86_64 Linux 4.4 .0-17763 - 微软
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 arg 会导致输出被截断(只是从 02666)?
(当使用python -u test.py > a.txt 将输出重定向到文件时,它可以正常工作。)

也许是关于缓冲的?

【问题讨论】:

  • 我可以确认 - 这很奇怪。 -u 确实 forces the binary I/O layers of stdout/stderr to be unbuffered - stdin is always buffered; text I/O layer will be line-buffered ..适用于您的输出 - 所以 maxline 大小似乎是 12222(这是您的输出的长度......) - linux 似乎在这里有更大的利润......
  • 感谢您的回复。我对此还有一些问题。 maxline 是什么意思? Windows 的常量? stdout 在没有缓冲区的情况下实际上做了什么?我在哪里可以找到有关此的更多信息?
  • maxline 正是我编造的,以表明 Windows 下的控制台似乎(在 -u 中)停止在 12222 个字符处写入标准输出 - 尽管我正在寻找我无法确认任何“硬“ 限制 - 谷歌没有帮助。也许一些 Windows-Crack 可以阐明这一点......
  • 好的,谢谢您的回复。
  • @eryksun 感谢您回答 OhYee 的发现 - 如果您想让它成为真正的答案,请联系我并投票。希望它很快就会成为一个过时的历史事实......谁再使用win7(除了一些陷入中年的客户);D

标签: python python-3.x windows powershell


【解决方案1】:

通过 WINAPI WriteFileWriteConsoleW 写入控制台的大小被记录为具有模糊定义的限制,如下所示:

nNumberOfCharsToWrite [in]
要写入的字符数。如果指定的总大小 字符数超过可用堆,函数失败 ERROR_NOT_ENOUGH_MEMORY

没有记录这指的是哪个“堆”。一个进程可以有多个不同大小的堆(固定的或动态的)。 NT 运行时库中的本机堆实现(例如RtlCreateHeap)可以在指定地址创建堆,从而可以方便地访问与其他进程共享的内存。使用共享堆通常与Local Inter-Process Communication (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。这是一种特殊的设计选择。我认为有人喝了太多的用户模式子系统,消息传递 Kool-Aid。正确的 NT I/O 使用具有 I/O 系统服务的设备,包括 NtCreateFileNtReadFileNtWriteFileNtDeviceIoControlFile

控制台应用程序不知道该堆中有多少可用于写入。 Python 可以从 64 KiB 开始并一直向下运行,但其 raw file I/O 要求每次调用一个系统调用。相反,它将写入上限设置为 32 KiB,这应该会成功。此限制允许编写具有多达 16K UTF-16 代码点的宽字符串。一个复杂的问题是控制台 I/O 堆栈在 3.6+ 中使用 UTF-8,必须通过 MultiByteToWideChar 解码。目前它只是重复地将 UTF-8 缓冲区分成两半,直到结果长度小于 16K。因此,在问题的示例中,写入 48,889 个字符将减半为 24,444 个字符,并再次减半为 12,222 个字符。 (IMO,最好尝试写入最多 16K 个代码点;获取实际写入的数字,并在子字符串上调用 WideCharToMultiByte 以确定写入的 UTF-8 字节数。当前设计实际上有一个错误,如果UTF-8 2-4 字节序列与切点重叠。)

在 NT 6.3+ (Windows 8.1+) 中,控制台 I/O 没有此大小限制,因为它使用 ConDrv 设备和 I/O 系统调用而不是 LPC。但是,不值得对代码进行特殊封装以支持由-u 命令行选项配置的无缓冲文本 I/O 堆栈。我们期望交互式控制台 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 中应删除控制台写入限制。

【讨论】:

    猜你喜欢
    • 2022-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    相关资源
    最近更新 更多