【问题标题】:How to escape from the standard output redirection in Python如何摆脱 Python 中的标准输出重定向
【发布时间】:2019-05-22 15:30:43
【问题描述】:

我想处理第三方脚本的实时输出,打印一些与模式匹配但跳过其他的行:

def thirdparty_code():
    from random import choice
    stuff = ['keep: important stuff', 'ignore: boring stuff']
    while True:
        chosen_line = choice(stuff)
        print(chosen_line)

我使用redirect_stdout(将行传递给我的虚拟 IO)和扩展的StringIO(用作 IO,但也调用我的过滤函数)。但是,当我在处理函数中调用 print() 时,我得到一个 RecursionError - 这并不意外:

from io import StringIO
from contextlib import redirect_stdout

class StringIOWithCallback(StringIO):

    def __init__(self, callback, **kwargs):
        super().__init__(**kwargs)
        self.callback = callback

    def write(self, s):
        super().write(s)
        self.callback(s)

def filter_boring_stuff_out(line):
    if line.startswith('ignore'):
        return
    print(line)

my_io = StringIOWithCallback(filter_boring_stuff_out)

with redirect_stdout(my_io):
    thirdparty_code()

我想知道是否可以从重定向中逃脱,例如在print() 函数中指定file 参数,以便它打印到实际的标准输出。我知道我可以轻松使用标准错误流:

import sys
print(line, file=sys.stderr)

但我特别想使用标准输出。有没有一种不错的 Pythonic 方式来做到这一点?

【问题讨论】:

  • 你可以通过一个函数来重定向 sys.stdout (和 std.err )来过滤东西。这通常通过日志记录来完成。尽管从外观上看,您提出的问题是您自己回答的。
  • 我该怎么做?我熟悉覆盖(或临时重定向)sys.stdout 以将输出作为字符串(例如,用于单元测试)以及使用 logger 模块的自定义日志功能(并按级别过滤日志)的人的做法。但是,两者都没有像问题中那样直接解决我的“实时”过滤问题。你能详细说明一下吗?
  • 关于我回答了这个问题的事实 - 由于我在写这篇文章时找到了答案而发布这个特定问题是错误的还是不鼓励?我的意思是,我read the piece 和一些关于meta 的帖子,但我对这是否是一个好的问答没有强烈的感觉。到目前为止,它至少部分达到了它的目的 - 我从另一个答案中学到了一些东西。
  • 如果您有答案但相信有更好的方法,那么只需将您的答案放在问题中并说出您不相信这是最好的方法的话。如果你随着时间的推移想出一个答案,那么就把它作为一个答案,但是如果你有一个答案,那么为什么要问这个问题呢?这是您在提出问题后无法在一定时间内标记答案的原因之一,人们只会提出问题,将答案放在那里,将其标记为解决方案,然后就结束了。 (即使这不是一个好的解决方案)。
  • 还要重定向stdout,所以print 将被过滤,请参阅下面的答案。

标签: python python-3.x contextmanager stringio


【解决方案1】:

重定向stdout 后,您可以轻松地重置它,这要归功于__stdout__,它保存了原始值。

sys.stdout = redirected_stdout
...
...
sys.stdout = sys.__stdout__

如果你发现自己不断地切换这些输出流,你应该创建一个函数来输出到重定向的流:

def redirect(*args):
    print(*args, file=redirected_file)

redirect(...)  # redirect this output
print(...)  # use standard stream

【讨论】:

  • 谢谢,sys.__stdout__的东西确实有用知道!
【解决方案2】:

您可以重定向stdout 并忽略所有以ignore 开头的消息。如果你这样做,所有prints 都将被拦截。如果您尝试从您无权访问或不想更改的代码中过滤消息,这会更好。

import sys
from contextlib import redirect_stdout

class Filter_Out:
    def __init__(self, *_, start=None, anywhere=None, end=None):
        self.last_ignore = False
        self.start = start
        self.anywhere = anywhere
        self.end = end
        self.terminal = sys.stdout
    def write(self, txt):
        if (self.start and txt.startswith(self.start)) or \
           (self.end and txt.endswith(self.end)) or \
           (self.anywhere and self.anywhere in txt):
            self.last_ignore = True
            return
        if self.last_ignore and txt == '\n':
            self.last_ignore = False
        else:
            self.terminal.write(txt)
    def flush(self):
        pass

with redirect_stdout(Filter_Out(start='ignore', anywhere='4')):
    print("test")
    print("test2")
    print("ignore: test2") # will not print because it started with ignore
    print("test1")
    print("test42") # will not print because it had 4 in it

【讨论】:

  • 有趣,这突出表明没有必要从 IO 继承。将标准输出保存在 init 中的想法(根据定义将在重定向之前执行)是一个很好的补充。
【解决方案3】:

写完我的问题后,我意识到在调用重定向之前只需将标准输出对象sys.stdout 保存到一个变量中:

stdout = sys.stdout

def filter_boring_stuff_out(line):
    if line.startswith('ignore'):
        return
    print(line, file=stdout)

但一如既往 - 我很乐意了解其他可能的解决方案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-07
    • 2016-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多