Matplotlib
前端、后端概念
Matplotlib
是采用面向对象方法设计的,绘图过程中的各种元素,如图像、子图、坐标轴、曲线等都有相应的类,通过类的接口函数和属性可以对图像和图像的各个组成元素进行控制。
Matplotlib
的绘图结果可以有各种输出形式,例如常见用的将绘图结果嵌入到wxpython、pygtk、Qt等GUI窗体中,或者将绘图结果输出为图片文件,或在Web应用程序中输出绘图结果。
为了便于用户实现这些不同的输出,Matplotlib
在设计上对用户编写的绘图代码和对不同输出形式的处理方法进行了隔离,因此出现了前端(frontend
)和后端的概念(backend
)。后端可以认为就是不同输出形式的处理功能,前端可以认为就是用户所要绘制的图像。
就像Web开发中的前后端分离一样,用户只用关心如何绘图即可,Matplotlib
会根据用户选择的后端进行输出。这样相同的前端绘图代码,就可以便捷地实现各种绘图输出。
Matplotlib
前后端分离原理
Matplotlib
中有四个模块与后端相关,matplotlib.backend_bases
,matplotlib.backend_managers
,matplotlib.backend_tools
和matplotlib.backends
,通过这四个模块Matplotlib
实现前后端分离。
matplotlib.backend_bases
模块:用于定义每个后端必须实现的六个抽象类。RendererBase
:底层渲染处理抽象类FigureCanvasBase
:图像与后端界面隔离抽象类。GraphicsContextBase
:颜色、线条样式功能抽象类。Event
:事件处理抽象类。ShowBase
:图像显示抽象类。ToolContainerBase
:工具栏抽象类。
matplotlib.backend_managers
:用于定义工具栏的相关类。matplotlib.backend_tools
:用于定义工具栏工具项的基类。matplotlib.backends
:用于定义各种不同后端的具体实现,每种不同实现均为单独的模块,例如matplotlib.backends.backend_pyqt5
模块为PyQT后端的抽象实现。
Matplotlib
后端分类
根据功能Matplotlib
的后端可以分为两种:
- 用户界面后端(也称为交互式后端),这类后端往往具有GUI界面可以与用户进行交互。例如用于wxpython、pygtk、tkinter、qt4、qt5、macosx的后端。对于用户界面后端,Matplotlib还将渲染器(renderer)和画布(canvas)分离开来,以实现更灵活的定制功能。Matplotlib使用的主要的渲染器是基于Anti-GrainGeometry C++库的
Agg
渲染器。除了macosx,所有的用户界面都使用Agg渲染器,因而有WXAgg、GTK3Agg、QT4Agg、QT5Agg、TkAgg等。有些用户界面也支持其他的渲染器,如Cairo
渲染器,因而有GTK3Cairo、QT4Cairo、QT5Cairo等。 - 用于生成图片文件的后端,如生成PNG、SVG、PDF等文件。
matplotlib.backends
模块中包含了各种不同后端的具体实现,matplotlib.backends
模块的目录结构如下:
backends
│ backend_agg.py
│ backend_cairo.py
│ backend_gtk3.py
│ backend_gtk3agg.py
│ backend_gtk3cairo.py
│ backend_macosx.py
│ backend_mixed.py
│ backend_nbagg.py
│ backend_pdf.py
│ backend_pgf.py
│ backend_ps.py
│ backend_qt4.py
│ backend_qt4agg.py
│ backend_qt4cairo.py
│ backend_qt5.py
│ backend_qt5agg.py
│ backend_qt5cairo.py
│ backend_svg.py
│ backend_template.py
│ backend_tkagg.py
│ backend_tkcairo.py
│ backend_webagg.py
│ backend_webagg_core.py
│ backend_wx.py
│ backend_wxagg.py
│ backend_wxcairo.py
│ qt_compat.py
│ _backend_agg.cp37-win_amd64.pyd
│ _backend_pdf_ps.py
│ _backend_tk.py
│ _tkagg.cp37-win_amd64.pyd
│ __init__.py
查看默认使用的后端
matplotlib.get_backend()
返回当前使用的后端的名称。
案例:
import matplotlib
print(matplotlib.get_backend())
结果
Qt5Agg
选择后端
matplotlib
与选择后端相关的主要函数有matplotlib.use()
函数和plt.switch_backend()
函数。
matplotlib.use(backend, *, force=True)
函数有两个参数:backend
为后端名称;force
为是否强制使用后端,如果后端不存在,则会抛出异常。
matplotlib.use()
函数执行流程如下:
- 通过
matplotlib.rcsetup
模块中的validate_backend
函数检查后端名称并返回后端名称。 - 检查
rcParams[backend]
的值是否与后端名称一致,一致退出检查,不一致继续流程。 - 检查是否导入
matplotlib.pyplot
模块,如果没有导入,将rcParams[backend]
的值修改为后端名称,如果已导入,尝试使用plt.switch_backend()
函数切换后端。
plt.switch_backend(backend)
函数只有一个参数,即后端名称。
plt.switch_backend(backend)
首先检测后端名称,如果名称不正常抛出错误,检测正常继续。- 随后导入后端对应模块,并创建相关对象。
- 将
matplotlib.backends.backend
设为后端名称。
由此可知,两者的区别在于:如果没有导入matplotlib.pyplot
模块,matplotlib.use()
函数仅修改rcParams[backend]
的值,而没有实际切换后端;如果已导入matplotlib.pyplot
模块,调用plt.switch_backend()
函数设置后端。
matplotlib
内置的后端名称
-
交互式后端:
GTK3Agg, GTK3Cairo, MacOSX, nbAgg,
Qt4Agg, Qt4Cairo, Qt5Agg, Qt5Cairo,
TkAgg, TkCairo, WebAgg, WX, WXAgg, WXCairo -
非交互式后端:
agg, cairo, pdf, pgf, ps, svg, template
matplotlib
切换后端案例
import matplotlib
import matplotlib.pyplot as plt
plt.switch_backend('tkagg')
print(matplotlib.get_backend())
print( matplotlib.backends.backend)
结果:
TkAgg
tkagg
matplotlib.use()
函数源码
def use(backend, *, force=True):
"""
Select the backend used for rendering and GUI integration.
Parameters
----------
backend : str
The backend to switch to. This can either be one of the standard
backend names, which are case-insensitive:
- interactive backends:
GTK3Agg, GTK3Cairo, MacOSX, nbAgg,
Qt4Agg, Qt4Cairo, Qt5Agg, Qt5Cairo,
TkAgg, TkCairo, WebAgg, WX, WXAgg, WXCairo
- non-interactive backends:
agg, cairo, pdf, pgf, ps, svg, template
or a string of the form: ``module://my.module.name``.
force : bool, default: True
If True (the default), raise an `ImportError` if the backend cannot be
set up (either because it fails to import, or because an incompatible
GUI interactive framework is already running); if False, ignore the
failure.
See Also
--------
:ref:`backends`
matplotlib.get_backend
"""
name = validate_backend(backend)
# we need to use the base-class method here to avoid (prematurely)
# resolving the "auto" backend setting
if dict.__getitem__(rcParams, 'backend') == name:
# Nothing to do if the requested backend is already set
pass
else:
# if pyplot is not already imported, do not import it. Doing
# so may trigger a `plt.switch_backend` to the _default_ backend
# before we get a chance to change to the one the user just requested
plt = sys.modules.get('matplotlib.pyplot')
# if pyplot is imported, then try to change backends
if plt is not None:
try:
# we need this import check here to re-raise if the
# user does not have the libraries to support their
# chosen backend installed.
plt.switch_backend(name)
except ImportError:
if force:
raise
# if we have not imported pyplot, then we can set the rcParam
# value which will be respected when the user finally imports
# pyplot
else:
rcParams['backend'] = backend
# if the user has asked for a given backend, do not helpfully
# fallback
rcParams['backend_fallback'] = False
plt.switch_backend()
函数源码
def switch_backend(newbackend):
"""
Close all open figures and set the Matplotlib backend.
The argument is case-insensitive. Switching to an interactive backend is
possible only if no event loop for another interactive backend has started.
Switching to and from non-interactive backends is always possible.
Parameters
----------
newbackend : str
The name of the backend to use.
"""
global _backend_mod
# make sure the init is pulled up so we can assign to it later
import matplotlib.backends
close("all")
if newbackend is rcsetup._auto_backend_sentinel:
current_framework = cbook._get_running_interactive_framework()
mapping = {'qt5': 'qt5agg',
'qt4': 'qt4agg',
'gtk3': 'gtk3agg',
'wx': 'wxagg',
'tk': 'tkagg',
'macosx': 'macosx',
'headless': 'agg'}
best_guess = mapping.get(current_framework, None)
if best_guess is not None:
candidates = [best_guess]
else:
candidates = []
candidates += ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
# Don't try to fallback on the cairo-based backends as they each have
# an additional dependency (pycairo) over the agg-based backend, and
# are of worse quality.
for candidate in candidates:
try:
switch_backend(candidate)
except ImportError:
continue
else:
rcParamsOrig['backend'] = candidate
return
else:
# Switching to Agg should always succeed; if it doesn't, let the
# exception propagate out.
switch_backend("agg")
rcParamsOrig["backend"] = "agg"
return
# Backends are implemented as modules, but "inherit" default method
# implementations from backend_bases._Backend. This is achieved by
# creating a "class" that inherits from backend_bases._Backend and whose
# body is filled with the module's globals.
backend_name = cbook._backend_module_name(newbackend)
class backend_mod(matplotlib.backend_bases._Backend):
locals().update(vars(importlib.import_module(backend_name)))
required_framework = _get_required_interactive_framework(backend_mod)
if required_framework is not None:
current_framework = cbook._get_running_interactive_framework()
if (current_framework and required_framework
and current_framework != required_framework):
raise ImportError(
"Cannot load backend {!r} which requires the {!r} interactive "
"framework, as {!r} is currently running".format(
newbackend, required_framework, current_framework))
_log.debug("Loaded backend %s version %s.",
newbackend, backend_mod.backend_version)
rcParams['backend'] = rcParamsDefault['backend'] = newbackend
_backend_mod = backend_mod
for func_name in ["new_figure_manager", "draw_if_interactive", "show"]:
globals()[func_name].__signature__ = inspect.signature(
getattr(backend_mod, func_name))
# Need to keep a global reference to the backend for compatibility reasons.
# See https://github.com/matplotlib/matplotlib/issues/6092
matplotlib.backends.backend = newbackend