Intercepting assignments to sys.stdout and sys.stderr

南笙酒味 提交于 2019-12-11 17:58:46

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/57022517/intercepting-assignments-to-sys-stdout-and-sys-stderr

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!