在运行时从正在运行的线程捕获stdout、stderr

问题描述 投票:0回答:1

我试图在运行时(即完成之前)从 exec() 调用中捕获 stdoutstderr 。我已将其包装在 contextlib 上下文下的线程中以重定向输出,但没有成功。 我试图避免子处理。以下代码描述了我正在尝试做的事情的简化版本。

import threading
from io import StringIO
import contextlib

code = "import time; print('hello'); time.sleep(2); print('working'); time.sleep(2); print('coming to an end'); time.sleep(2); print('end')"
f = StringIO()
e = StringIO()
    
with contextlib.redirect_stdout(f), contextlib.redirect_stderr(e):
    t1 = threading.Thread(target=exec, args=(code, globals(), locals()))
    t1.start()
    while t1.is_alive(): 
        print(f.getvalue())
        print(e.getvalue())
    t1.join()
python multithreading exec stdout python-multithreading
1个回答
0
投票

如果您将

print
与默认文件参数一起使用,那么它将打印到
sys.stdout
,我们已将
contextlib.redirect_stdout
更改为
f
,导致您的代码将
f.getvalue()
的结果写回到
f
。 因此以下陈述在上下文中是等效的。

>>> print(f.getvalue())
>>> f.write(f.getvalue())

您可以使用

file=sys.__stdout__
访问正常的标准输出。不应使用 contextlib.redirect_stdout 覆盖它。如果您想立即看到输出,那么您可能需要将
flush
设置为
True
。此外
f.getvalue()
应该有自己的换行符,这样我们的打印就不必附加换行符,所以我们设置
end=""

>>> print(f.getvalue(), file=sys.__stdout__, flush=True, end="")

但是现在你可能会遇到一个新问题: 当线程处于活动状态时,您可以像 python 一样频繁地打印整个

StringIO
缓冲区:
"hello, hello, ..., hello working, hello working ..."
。 您可以通过两种方式解决这个问题。 要么等到线程完成,然后打印
f.getvalue()
一次。在这种情况下,您甚至不必费心
sys.__stdout__
。 因为您可以将
print(f.getvalue())
放在标准输出上下文之外。但这意味着在线程完成之前您将无法访问文本。对于您的用例来说,这可能很烦人,因为文本已经写入缓冲区。因此,解决此问题的另一种方法是覆盖替换 stdout 的类文件对象的 write 方法。可能看起来像这样:

from contextlib import redirect_stdout
from io import StringIO
import sys
import threading

class get_and_print(StringIO):
    def write(self, *args):
        print(*args, file=sys.__stdout__, flush=True, end="") # print directly to the terminal
        return super().write(*args) # update the internal buffer using the write method of the base class StringIO

code = "import time; print('hello'); time.sleep(2); print('working'); time.sleep(2); print('coming to an end'); time.sleep(2); print('end')"

f = get_and_print()

with redirect_stdout(f):
    t1 = threading.Thread(target=exec, args=(code, globals(), locals()))
    t1.start()
    # we just wait until the tread completes because t1 is responsible for printing to sys.stdout
    t1.join()

if False:
    # we still can print f.getvalue later
    print(f.getvalue())

现在您可以在文本写入后立即访问该文本。并且只有附加文本。但是,一旦线程完成,您仍然可以稍后调用

f.getvalue
来获取所有文本。

© www.soinside.com 2019 - 2024. All rights reserved.