Python os.dup2 重定向在 windows python 控制台上启用输出缓冲

Python os.dup2 redirect enables output buffering on windows python consoles

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

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

捕获前 sys.stdout 是 returns Trueistty() 的某种文件对象。输入 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?')

在刷新标准输出之前不显示输出:

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

有人知道发生了什么事吗?


快速信息:

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

没有

基本问题是

>>> sys.stdout is sys.__stdout__
True

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

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


因此 Capture class 的正确写法是

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

第一行立即出现在终端上,第二行在延迟一秒后出现。


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