拦截对 sys.stdout 和 sys.stderr 的赋值 [英] Intercepting assignments to sys.stdout and sys.stderr

查看:40
本文介绍了拦截对 sys.stdout 和 sys.stderr 的赋值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

sys 模块有几个我感兴趣的全局属性:sys.stdoutsys.stderr.

我正在构建我自己的模块,它(除其他外)用自己的包装器替换 sys.stdoutsys.stderr 来拦截尝试的输出,修改它,然后将其转发到原件.我这样做的方法是这样的:

_orig_stdout = sys.stdout_orig_stderr = sys.stderrsys.stdout = MyFakeStdoutClass()sys.stderr = MyFaleStderrClass()

这按预期工作 - 在我的模块导入后的任何时候,尝试对 sys.stdoutsys.stderr 执行任何操作都会通过我的类.

<小时>

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

对于普通班级,这样做很容易 - 我只需要覆盖班级的 __setattr__() 方法:

_orig_setattr = OtherClass.__setattr__def my_setattr(obj, name, value):如果名称 != "stdout":_orig_setattr(对象,名称,值)OtherClass.__setattr__ = my_setattr

但是,我已经尝试为 sys 模块本身执行此操作,但它不起作用(即使在我执行 sys.__setattr__ = my_setattr 之后,我发现my_setattr 永远不会被调用).

此外,其他答案指出为 sys 模块制作我自己的包装类并将其分配给 sys.modules['sys'] 的可能解决方案,这将不起作用 - 如果 sys 在我的模块被该模块导入之前被导入到另一个模块中(这很可能),那么我的更改不会坚持.

此外,使用一些辅助方法设置 sys.stdout = property(stdout_getter, stdout_setter) 以返回/修改我的 _orig_stdout 变量也不起作用.即使稍后在同一个文件中,我也可以执行 sys.stdout = sys.__stdout__,然后就恢复正常了.我不希望这成为可能.

有什么办法可以绕过这个限制吗?

解决方案

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

下面是一个跨平台示例,展示了如何覆盖标准输出,允许对写入其中的所有数据使用自定义逻辑.在这个例子中,逻辑只是复制写入标准输出的所有字符.

import os导入系统进口螺纹def handle_stdout(fake_stdout, real_stdout):虽然不是 fake_stdout.closed 也不是 real_stdout.closed:char = fake_stdout.read(1)real_stdout.write(char * 2)def override_stdout():标准输出_fd = 1pipe_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)返回 pipe_reader, original_stdout_writer# 覆盖标准输出pipe_reader, original_stdout_writer = override_stdout()thread = threading.Thread(target=handle_stdout, args=(pipe_reader, original_stdout_writer))线程开始()# 写东西到标准输出打印('foobar')# 清理以允许后台线程关闭pipe_reader.close()original_stdout_writer.close()

运行此示例将输出:

ffoooobbaarr

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

The sys module has a couple of global properties that I'm interested in: sys.stdout and sys.stderr.

I'm building a module of my own, that (among other things), replaces sys.stdout and sys.stderr with its own wrappers that intercept attempted output, modify it, and then forward it to the originals. My method for doing so is something like this:

_orig_stdout = sys.stdout
_orig_stderr = sys.stderr
sys.stdout = MyFakeStdoutClass()
sys.stderr = MyFaleStderrClass()

This works as expected - at any time after my module is imported, trying to do anything with sys.stdout or sys.stderr goes through my class instead.


Now, my module has a vested interest in making sure that this arrangement stays how it is now - it wants to keep control of sys.stdout and sys.stderr permanently. Other modules can reassign sys.stdout the same way that my module did, and my module doesn't want them to be able to do that. Instead, my module wants to intercept their attempt to do so.

For a regular class, doing this would be easy - I'd just have to overwrite the class's __setattr__() method:

_orig_setattr = OtherClass.__setattr__
def my_setattr(obj, name, value):
    if name != "stdout":
        _orig_setattr(obj, name, value)
OtherClass.__setattr__ = my_setattr

However, I've tried doing this for the sys module itself, and it doesn't work (even after I do sys.__setattr__ = my_setattr, I find that my_setattr never gets called).

Additionally, while other answers point out the possible solution of making my own wrapper class for the sys module and assigning it to sys.modules['sys'], this won't work - if sys was imported in another module before my module is imported by that module (which is likely), then my change won't stick.

Furthermore, setting sys.stdout = property(stdout_getter, stdout_setter) with some helper methods to return/modify my _orig_stdout variable didn't work either. Even later in the same file, I could just do sys.stdout = sys.__stdout__, and it was back to normal. I don't want this to be possible.

Is there a good way around this limitation?

解决方案

The only real way to completely override stdout on python is to actually override the stdout file descriptor (1). This can be done using the dup2 syscall.

Below is a cross-platform example showing how to override stdout, allowing to use custom logic on all data written to it. In this example, the logic just duplicates all characters written to 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()

Running this example will output:

ffoooobbaarr

For some versions of python, you'll have to set the PYTHONLEGACYWINDOWSSTDIO environment variable to a non-empty string to make this example to work on windows.

这篇关于拦截对 sys.stdout 和 sys.stderr 的赋值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆