Python os.dup2 redirect在Windows python控制台上启用输出缓冲

时间:2015-03-20 13:24:57

标签: python stdout flush buffering dup2

我使用基于os.dup2的策略(类似于此站点上的示例)将C / fortran级别输出重定向到临时文件以进行捕获。

我唯一注意到的问题是,如果你从windows中的交互式shell(python.exe或ipython)中使用此代码,它会在控制台中启用启用输出缓冲的奇怪副作用/ em>的

捕获之前sys.stdout是某种为True返回istty()的文件对象。键入print('hi')会导致hi直接输出。 捕获后sys.stdout指向完全相同的文件对象,但print('hi')在调用sys.stdout.flush()之前不再显示任何内容。

下面是一个最小的示例脚本" test.py"

import os, sys, tempfile

class Capture(object):
    def __init__(self):
        super(Capture, self).__init__()
        self._org = None    # Original stdout stream
        self._dup = None    # Original system stdout descriptor
        self._file = None   # Temporary file to write stdout to

    def start(self):
        self._org = sys.stdout
        sys.stdout = sys.__stdout__
        fdout = sys.stdout.fileno()
        self._file = tempfile.TemporaryFile()
        self._dup = None
        if fdout >= 0:
            self._dup = os.dup(fdout)
            os.dup2(self._file.fileno(), fdout)

    def stop(self):
        sys.stdout.flush()
        if self._dup is not None:
            os.dup2(self._dup, sys.stdout.fileno())
            os.close(self._dup)
        sys.stdout = self._org
        self._file.seek(0)
        out = self._file.readlines()
        self._file.close()
        return out

def run():
    c = Capture()
    c.start()
    os.system('echo 10')
    print('20')
    x = c.stop()
    print(x)

if __name__ == '__main__':
    run()

打开命令提示符并运行脚本可以正常工作。这会产生预期的输出:

python.exe test.py

从python shell运行它不会:

python.exe
>>> import test.py
>>> test.run()
>>> print('hello?')

直到刷新stdout才会显示输出:

>>> import sys
>>> sys.stdout.flush()

有人知道发生了什么吗?


快速信息:

  • 问题出现在Windows上,不是在Linux上(所以可能不在Mac上)。
  • Python 2.7.6和Python 2.7.9中的相同行为
  • 脚本应该捕获C / fortran输出,而不仅仅是python输出
  • 它在Windows上运行没有错误,但之后print()不再刷新

1 个答案:

答案 0 :(得分:2)

可以确认Linux中的Python 2的相关问题,但不能用Python 3确认

基本问题是

>>> sys.stdout is sys.__stdout__
True

因此,您始终使用原始sys.stdout对象 。当您执行第一次输出时,在Python 2中,它会对基础文件执行一次isatty()系统调用,并存储结果。

您应该打开一个全新的文件并用它替换sys.stdout


因此,编写Capture类的正确方法是

import sys
import tempfile
import time
import os

class Capture(object):
    def __init__(self):
        super(Capture, self).__init__()

    def start(self):
        self._old_stdout = sys.stdout
        self._stdout_fd = self._old_stdout.fileno()
        self._saved_stdout_fd = os.dup(self._stdout_fd)
        self._file = sys.stdout = tempfile.TemporaryFile(mode='w+t')
        os.dup2(self._file.fileno(), self._stdout_fd)

    def stop(self):
        os.dup2(self._saved_stdout_fd, self._stdout_fd)
        os.close(self._saved_stdout_fd)
        sys.stdout = self._old_stdout
        self._file.seek(0)
        out = self._file.readlines()
        self._file.close()
        return out

def run():
    c = Capture()
    c.start()
    os.system('echo 10')
    x = c.stop()
    print(x)
    time.sleep(1)
    print("finished")

run()

使用这个程序,在Python 2和Python 3中,输出将是:

['10\n']
finished

第一条线瞬间出现在终端上,第二条线在一秒钟后出现延迟。


但是,对于从stdout导入sys的代码,这会失败。幸运的是没有多少代码可以做到这一点。