【问题标题】:unbuffered read from stdin in python从python中的标准输入无缓冲读取
【发布时间】:2015-10-23 14:41:29
【问题描述】:

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

batch_job | myparser

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

这个问题已经在hereherehere讨论过了。

我尝试了以下方法:

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

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

【问题讨论】:

  • 您确定缓冲发生在 Python 中的标准输入上,而不是批处理作业的标准输出上吗?有时应用程序会检查标准输出的设备类型,并将其缓冲基于它是什么,所以仅仅因为它在写入终端时可能看起来是行缓冲并不意味着它在通过管道传输到另一个进程时也会这样做。跨度>
  • 这是一个有趣的建议。我将尝试验证。我能说的是应用程序本身就是shell脚本。
  • 它还会创建一个日志文件,其内容与通常写入终端的内容相同。我观察到这个日志文件更新得更快,即它已经包含我的脚本仍在等待的行。
  • 为什么不直接从myparser 中启动batch_job 作为子进程,然后您就可以完全控制STDOUT/STDIN?您设置它的方式不仅取决于 Python,还取决于 shell 缓冲本身。

标签: python stdin unbuffered


【解决方案1】:

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

当你有一个循环的表单时,问题就出现了:

for line in sys.stdin:

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

有三种可能的修复方法:

  1. (仅在 Python 2.6+ 上)使用 io 模块(作为内置从 Python 3 向后移植)重新包装 sys.stdio 以绕过 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 )str.

  2. (任何版本的 Python)通过使用 file.readline 来解决 file.__next__ 的问题;尽管预期的行为几乎相同,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。 :-)

【讨论】:

  • 注意:显然,如果batch_job 有缓冲输出,您需要取消缓冲它或确保它进行手动刷新,以便 Python 程序可以看到任何内容。但我确实见过前面的进程肯定没有缓冲的情况,而 Python 2 的 for line in sys.stdin: 负责缓冲(非 Python 2 程序使用原始 I/ O 或普通 C stdio,没问题)。
【解决方案2】:

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

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

# batch_job | stdbuf -o0 myparser

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

# batch_job | stdbuf -oL myparser

【讨论】:

  • 这无济于事。问题不在于 Python 的输出缓冲(如果是,-u 标志或在调用脚本之前执行 export PYTHONUNBUFFERED=1 将修复它;stdbuf [命令行工具] 不适用于修改默认stdio 缓冲setvbuf/setbuf [API] 在任何情况下,Python 可以并且确实这样做),它 Python 缓冲 输入。并且输入的缓冲是在stdbuf(命令行工具)无法影响的 Python 用户模式缓冲区中完成的。
  • @ShadowRanger 好吧,它确实有效。我通过在两个 python 程序之间提供数据来测试这一点,有和没有 stdbu -o0,差异非常明显。所以,事实就是这样。而且你根据自己的猜测投反对票而不尝试是不公平的。
  • 它可能在某些情况下有效,但在 -u/PYTHONUNBUFFERED=1 已经没有帮助的情况下,它不适用于 Python 2.x。您可能被与 OP 不同的测试用例所愚弄(例如,在您的情况下,您的输入管道也是 Python;OP 仅用于管道的输出)。简单的bash 单行示例不起作用:(for ((i = 0; i < 10; ++i)); do echo $i && sleep 1; done) | stdbuf -o0 python2 -c 'for line in __import__("sys").stdin: print line,';您在 10 秒内没有任何输出。原因是file.__next__ 中的缓冲,stdbuf 不影响。
  • __import__("sys").stdin 替换为iter(__import__("sys").stdin.readline, ""),您将每秒得到一个输出。如果您可以向我展示一个示例,其中管道右侧的 stdbuf 解决了 OP 尝试的各种事情未解决的问题,我会很乐意将我的反对票转换为赞成票。但我认为不存在这种情况(如stdbuf's man page notes“如果 COMMAND 调整其标准流的缓冲(例如,'tee' 所做的那样),那么这将覆盖由 'stdbuf' 更改的相应设置。”;Python 2 做到了)。
  • @ShadowRanger 输入来自 C 程序,是的,它是 python2。它是一个部署的成像系统,从 C 到 MIMO 阵列,到 python 中的图像显示和 AI。
【解决方案3】:

您可以取消缓冲输出:

unbuffer batch_job | myparser

【讨论】:

    猜你喜欢
    • 2011-05-03
    • 2011-05-18
    • 1970-01-01
    • 2011-01-23
    • 2020-10-05
    • 1970-01-01
    • 2015-07-05
    • 2020-08-04
    • 1970-01-01
    相关资源
    最近更新 更多