拦截对 sys.stdout 和 sys.stderr 的赋值 [英] Intercepting assignments to sys.stdout and sys.stderr
问题描述
sys
模块有几个我感兴趣的全局属性:sys.stdout
和 sys.stderr
.
我正在构建我自己的模块,它(除其他外)用自己的包装器替换 sys.stdout
和 sys.stderr
来拦截尝试的输出,修改它,然后将其转发到原件.我这样做的方法是这样的:
_orig_stdout = sys.stdout_orig_stderr = sys.stderrsys.stdout = MyFakeStdoutClass()sys.stderr = MyFaleStderrClass()
这按预期工作 - 在我的模块导入后的任何时候,尝试对 sys.stdout
或 sys.stderr
执行任何操作都会通过我的类.
现在,我的模块有兴趣确保这种安排保持现在的状态 - 它希望保持对 sys.stdout
和 sys.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屋!