gpt4 book ai didi

python - 通过 python 或 ipython 终端运行 .py 文件时抑制 matplotlib 图形

转载 作者:行者123 更新时间:2023-11-28 17:40:25 26 4
gpt4 key购买 nike

我正在编写一个 test_examples.py 来测试 python 示例文件夹的执行。目前我使用 glob 来解析文件夹,然后使用 subprocess 来执行每个 python 文件。问题是这些文件中的一些是绘图,它们会打开一个 Figure 窗口,该窗口会暂停直到窗口关闭。

关于这个问题的很多问题都提供了文件内部的解决方案,但是我如何在不进行任何修改的情况下在外部运行文件的同时抑制输出?

到目前为止我所做的是:

import subprocess as sb
import glob
from nose import with_setup

def test_execute():
files = glob.glob("../*.py")
files.sort()
for fl in files:
try:
sb.call(["ipython", "--matplotlib=Qt4", fl])
except:
assert False, "File: %s ran with some errors\n" % (fl)

这种方法有效,因为它抑制了数字,但它不会抛出任何异常(即使程序有错误)。我也不是 100% 确定它在做什么。它是将所有图形附加到 Qt4,还是在该脚本完成后将图形从内存中删除?

理想情况下,我想理想地运行每个 .py 文件并捕获其 stdoutstderr,然后使用退出条件来报告stderr 并使测试失败。然后,当我运行 nosetests 时,它将运行程序的示例文件夹并检查它们是否全部运行。

最佳答案

您可以通过在每个源文件的顶部插入以下行来强制 matplotlib 使用 Agg 后端(不会打开任何窗口):

import matplotlib
matplotlib.use('Agg')

这是一个单行 shell 命令,它将在将输出通过管道传输到用于执行的 Python 解释器:

~$ sed "1i import matplotlib\nmatplotlib.use('Agg')\n" my_script.py | python

您应该能够使用 subprocess 进行等效调用,如下所示:

p1 = sb.Popen(["sed", "1i import matplotlib\nmatplotlib.use('Agg')\n", fl],
stdout=sb.PIPE)
exit_cond = sb.call(["python"], stdin=p1.stdout)

您可以通过传递 stdout=stderr= 参数从脚本中捕获 stderrstdoutsb.call()。当然,这只适用于具有 sed 实用程序的 Unix 环境。


更新

这其实是一个很有趣的问题。我仔细考虑了一下,我认为这是一个更优雅的解决方案(尽管仍然有点 hack):

#!/usr/bin/python

import sys
import os
import glob
from contextlib import contextmanager
import traceback

set_backend = "import matplotlib\nmatplotlib.use('Agg')\n"

@contextmanager
def redirected_output(new_stdout=None, new_stderr=None):
save_stdout = sys.stdout
save_stderr = sys.stderr
if new_stdout is not None:
sys.stdout = new_stdout
if new_stderr is not None:
sys.stderr = new_stderr
try:
yield None
finally:
sys.stdout = save_stdout
sys.stderr = save_stderr

def run_exectests(test_dir, log_path='exectests.log'):

test_files = glob.glob(os.path.join(test_dir, '*.py'))
test_files.sort()
passed = []
failed = []
with open(log_path, 'w') as f:
with redirected_output(new_stdout=f, new_stderr=f):
for fname in test_files:
print(">> Executing '%s'" % fname)
try:
code = compile(set_backend + open(fname, 'r').read(),
fname, 'exec')
exec(code, {'__name__':'__main__'}, {})
passed.append(fname)
except:
traceback.print_exc()
failed.append(fname)
pass

print ">> Passed %i/%i tests: " %(len(passed), len(test_files))
print "Passed: " + ', '.join(passed)
print "Failed: " + ', '.join(failed)
print "See %s for details" % log_path

return passed, failed

if __name__ == '__main__':
run_exectests(*sys.argv[1:])

从概念上讲,这与我之前的解决方案非常相似 - 它的工作原理是将测试脚本作为字符串读取,并在它们前面添加几行,这些行将导入 matplotlib 并将后端设置为非交互式。然后将字符串编译为 Python 字节码,然后执行。主要优点是它应该与平台无关,因为不需要 sed

{'__name__':'__main__'} 全局变量的技巧是必要的,如果你像我一样倾向于这样写你的脚本:

    def run_me():
...
if __name__ == '__main__':
run_me()

需要考虑的几点:

  • 如果您尝试从已经导入 matplotlib 并设置交互式后端的 ipython session 中运行此函数,则 set_backend 技巧将不起作用,您仍然会弹出数字向上。最简单的方法是直接从 shell (~$ python exectests.py testdir/logfile.log) 运行它,或者从您没有为matplotlib。如果您在 ipython session 中的不同子进程中运行它,它也应该可以工作。
  • 我正在使用来自 this answercontextmanager 技巧将 stdinstdout 重定向到日志文件。请注意,这不是线程安全的,但我认为脚本打开子进程是很不寻常的。

关于python - 通过 python 或 ipython 终端运行 .py 文件时抑制 matplotlib 图形,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25215477/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com