14

我正在编写一个 python 脚本,它可以通过管道从另一个命令读取输入,就像这样

batch_job | myparser

我的脚本myparser处理输出batch_job并写入它自己的标准输出。我的问题是我想立即查看输出(batch_job 的输出是逐行处理的),但似乎有这个臭名昭著的标准输入缓冲(据称是 4KB,我尚未验证)延迟了一切。

这个问题已经在这里这里讨论

我尝试了以下方法:

  • 使用打开标准输入os.fdopen(sys.stdin.fileno(), 'r', 0)
  • -u在我的 hashbang 中使用:#!/usr/bin/python -u
  • export PYTHONUNBUFFERED=1在调用脚本之前设置
  • 在读取的每一行之后刷新我的输出(以防问题来自输出缓冲而不是输入缓冲)

我的 python 版本是 2.4.3 - 我不可能升级或安装任何额外的程序或包。我怎样才能摆脱这些延迟?

4

3 回答 3

2

我在遗留代码中遇到了同样的问题。这似乎是 Python 2 的file对象__next__方法的实现问题;它使用 Python 级别的缓冲区(这-u/PYTHONUNBUFFERED=1不会影响,因为那些只是取消stdio FILE*s 本身的缓冲,但file.__next__' 的缓冲不相关;同样,stdbuf/unbuffer根本无法更改任何缓冲,因为 Python 替换了默认值由 C 运行时创建的缓冲区;对file.__init__新打开的文件所做的最后一件事是调用PyFile_SetBufSize,它使用setvbuf/ setbuf[API] 来替换默认stdio缓冲区)。

当您有以下形式的循环时,就会出现问题:

for line in sys.stdin:

其中第一次调用__next__(由循环隐式调用for以获取 each line)最终阻塞以在生成单行之前填充块。

有三种可能的修复方法:

  1. (仅在 Python 2.6+ 上)sys.stdio使用io模块重新包装(作为内置从 Python 3 向后移植)以file完全绕过(坦率地说是优越的)Python 3 设计(一次使用单个系统调用来填充缓冲区没有阻塞发生完整请求的读取;如果它要求 4096 字节并获得 3,它将查看一行是否可用,如果可用则生成它)所以:

    import io
    import sys
    
    # Add buffering=0 argument if you won't always consume stdin completely, so you 
    # can't lose data in the wrapper's buffer. It'll be slower with buffering=0 though.
    with io.open(sys.stdin.fileno(), 'rb', closefd=False) as stdin:
        for line in stdin:
            # Do stuff with the line
    

    这通常比选项 2 更快,但更冗长,并且需要 Python 2.6+。它还允许重新包装是 Unicode 友好的,通过将模式更改为'r'并可选地传递已知encoding的输入(如果它不是区域设置默认值)以无缝获取unicode行而不是 (ASCII only) str

  2. (任何版本的 Python)file.__next__通过使用file.readline来解决问题;尽管预期的行为几乎相同,readline但不会进行自己的(过度)缓冲,它会委托给 C stdiofgets默认构建设置)或手动循环调用getc/getc_unlocked进入缓冲区,该缓冲区在到达行尾时准确停止。通过将它与两个参数组合,iter您可以获得几乎相同的代码而不会过多冗长(它可能会比以前的解决方案慢,具体取决于是否fgets在后台使用,以及 C 运行时如何实现它):

    # '' is the sentinel that ends the loop; readline returns '' at EOF
    for line in iter(sys.stdin.readline, ''):
        # Do stuff with line
    
  3. 转移到没有这个问题的 Python 3。:-)

于 2020-11-20T18:10:00.967 回答
0

您可以取消缓冲输出:

unbuffer batch_job | myparser
于 2020-11-27T10:03:18.740 回答
0

在 Linux 中,bash,您正在寻找的似乎是 stdbuf 命令。

如果你不想缓冲(即无缓冲的流),试试这个,

# batch_job | stdbuf -o0 myparser

如果你想要行缓冲,试试这个,

# batch_job | stdbuf -oL myparser
于 2018-09-18T15:26:34.270 回答