我试图在运行时(即完成之前)从 exec() 调用中捕获 stdout 和 stderr 。我已将其包装在 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()
如果您将
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
来获取所有文本。