您可以通过在每个源文件的顶部插入以下行来强制 matplotlib 使用 Agg 后端(不会打开任何窗口):
import matplotlib
matplotlib.use('Agg')
这是一个单行 shell 命令,它将动态在my_script.py 的顶部插入这些行(不修改磁盘上的文件),然后将输出传送到 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= 参数传递给sb.call(),从脚本中捕获stderr 和stdout。当然,这仅适用于具有 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 会话中运行此函数,
set_backend 技巧将不起作用,您仍会弹出数字。最简单的方法是直接从 shell (~$ python exectests.py testdir/ logfile.log) 或从没有为 matplotlib 设置交互式后端的 (i)python 会话运行它。如果您在 ipython 会话中的不同子进程中运行它,它也应该可以工作。
- 我正在使用来自this answer 的
contextmanager 技巧将stdin 和stdout 重定向到日志文件。请注意,这不是线程安全的,但我认为脚本打开子进程是很不寻常的。