拦截对sys.stdout和sys.stderr的分配

时间:2019-07-13 20:13:42

标签: python python-3.x variable-assignment

<a class="MuiTypography-root MuiLink-root MuiLink-underlineHover MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button MuiTypography-colorPrimary" href="/"> 模块具有几个我感兴趣的全局属性:syssys.stdout

我正在构建自己的模块,该模块(除其他外)将sys.stderrsys.stdout替换为其自己的包装器,以拦截尝试的输出,对其进行修改,然后将其转发至原件。我这样做的方法是这样的:

sys.stderr

这可以按预期的方式工作-导入模块后的任何时候,尝试对_orig_stdout = sys.stdout _orig_stderr = sys.stderr sys.stdout = MyFakeStdoutClass() sys.stderr = MyFaleStderrClass() sys.stdout进行的操作都会遍历我的类。


现在,我的模块对确保这种安排保持现在的状态非常感兴趣-它希望永久控制sys.stderrsys.stdout。其他模块可以像我的模块一样重新分配sys.stderr,而我的模块不希望它们能够这样做。相反,我的模块希望拦截他们的尝试。

对于常规类,这样做很容易-我只需要覆盖类的sys.stdout方法:

__setattr__()

但是,我尝试对_orig_setattr = OtherClass.__setattr__ def my_setattr(obj, name, value): if name != "stdout": _orig_setattr(obj, name, value) OtherClass.__setattr__ = my_setattr 模块本身执行此操作,但是它不起作用(即使在执行sys之后,我仍然发现sys.__setattr__ = my_setattr从未被调用)

此外,尽管other answers指出了为my_setattr模块创建自己的包装器类并将其分配给sys的可能解决方案,但这将不起作用-如果{{1 }}是在另一个模块中导入我的模块之前导入的(很可能),然后我的更改不会保留。

此外,使用一些辅助方法设置sys.modules['sys']来返回/修改我的sys变量也不起作用。甚至在同一文件的稍后部分,我也可以执行sys.stdout = property(stdout_getter, stdout_setter),它恢复了正常。我不希望这样。

有没有解决此限制的好方法?

1 个答案:

答案 0 :(得分:1)

完全覆盖python上stdout的唯一真实方法是实际覆盖stdout文件描述符(1)。可以使用dup2系统调用来完成。

下面是一个跨平台示例,显示了如何覆盖stdout,从而允许对写入到其中的所有数据使用自定义逻辑。在此示例中,逻辑仅复制所有写入标准输出的字符。

import os
import sys
import threading

def handle_stdout(fake_stdout, real_stdout):
    while not fake_stdout.closed and not real_stdout.closed:
        char = fake_stdout.read(1)
        real_stdout.write(char * 2)

def override_stdout():
    stdout_fd = 1
    pipe_reader_fd, pipe_writer_fd = os.pipe()
    pipe_reader = os.fdopen(pipe_reader_fd, 'r')
    original_stdout_writer = os.fdopen(os.dup(stdout_fd), 'w')
    os.dup2(pipe_writer_fd, stdout_fd)
    return pipe_reader, original_stdout_writer

# Override stdout
pipe_reader, original_stdout_writer = override_stdout()
thread = threading.Thread(target=handle_stdout, args=(pipe_reader, original_stdout_writer))
thread.start()

# Write stuff to stdout
print('foobar')

# Cleanup to allow background thread to shut down
pipe_reader.close()
original_stdout_writer.close()

运行此示例将输出:

ffoooobbaarr

对于某些版本的python,您必须将PYTHONLEGACYWINDOWSSTDIO环境变量设置为非空字符串才能使该示例在Windows上运行。