在python中从stdin读取无缓冲的

时间:2015-10-23 14:41:29

标签: python stdin unbuffered

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

batch_job | myparser

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

问题已经在here herehere进行了讨论。

我尝试了以下内容:

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

我的python版本是2.4.3 - 我无法升级或安装任何其他程序或软件包。我怎样才能摆脱这些延误?

3 个答案:

答案 0 :(得分:1)

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

如果您不希望缓冲(即无缓冲的流),请尝试此操作

# batch_job | stdbuf -o0 myparser

如果要行缓冲,请尝试此操作

# batch_job | stdbuf -oL myparser

答案 1 :(得分:0)

我在旧版代码中遇到了同样的问题。 Python 2的file对象的__next__方法的实现似乎存在问题。它使用Python级别的缓冲区(-u / PYTHONUNBUFFERED=1不会影响,因为那些缓冲区本身仅对stdio FILE*进行缓冲,而对file.__next__缓冲没有关系;类似地,stdbuf / unbuffer根本无法更改任何缓冲,因为Python替换了C运行时创建的默认缓冲;最后file.__init__对于新打开的文件,调用PyFile_SetBufSize,它使用setvbuf / setbuf [API]替换默认的stdio缓冲区)。

当您使用以下形式的循环时,就会看到问题:

for line in sys.stdin:

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

有三种可能的修复方法:

  1. (仅在Python 2.6+上)使用sys.stdio模块(从Python 3作为内置返回源)重新包装io以完全绕过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+。通过将模式更改为'r'并有选择地传递输入的已知encoding(如果不是语言环境默认值)以无缝获取unicode,它还允许重新包装对Unicode友好行而不是(仅适用于ASCII)str

  2. (任何版本的Python)都可以使用file.__next__来解决file.readline的问题;尽管预期行为几乎相同,但是readline并没有做自己的(过度)缓冲,它委托给C stdio的{​​{1}}(默认构建设置)或手动循环调用{{ 1}} / fgets放入缓冲区,该缓冲区在到达行尾时会完全停止。通过将其与两个参数getc结合使用,您可以得到几乎相同的代码而不会产生过多的冗长性(它可能比以前的解决方案要慢,这取决于是否在引擎盖下使用getc_unlocked以及如何使用C运行时将其实现):

    iter
  3. 移动到没有此问题的Python 3。 :-)

答案 2 :(得分:0)

您可以取消输出缓冲:

unbuffer batch_job | myparser