在 Matplotlib 3.1.2 中使用 qt5agg 后端,get_backend() 会改变行为

Posted

技术标签:

【中文标题】在 Matplotlib 3.1.2 中使用 qt5agg 后端,get_backend() 会改变行为【英文标题】:Using qt5agg backend with Matplotlib 3.1.2, get_backend() changes behavior 【发布时间】:2020-01-09 02:58:55 【问题描述】:

我一直在使用一个在 Qt 窗口中嵌入 Matplotlib 图形的项目,当我将 Matplotlib 更新到高于 3.1.0rc1 的版本时发现它失败了。我创建了一个最小的工作示例 (testImports.py):

import matplotlib

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar

import matplotlib.pyplot as plt
import numpy as np

f = plt.figure()
x = np.arange(0, 10, 0.001)
ysin = np.sin(x)
plt.plot(x, ysin, '--')
plt.show()

使用 Matplotlib 3.1.0rc1 或更早版本,它可以正常运行,显示图形。使用 Matplotlib 3.1.0rc2 或更高版本时,出现以下错误(请注意,我在虚拟环境中工作,并且已将路径的根更改为 test_env):

Traceback (most recent call last):
  File "testImports.py", line 14, in <module>
    f = plt.figure()
  File "test_env/lib/python3.6/site-packages/matplotlib/pyplot.py", line 534, in figure
    **kwargs)
  File "test_env/lib/python3.6/site-packages/matplotlib/backend_bases.py", line 3249, in new_figure_manager
    return cls.new_figure_manager_given_figure(num, fig)
  File "test_env/lib/python3.6/site-packages/matplotlib/backend_bases.py", line 3255, in new_figure_manager_given_figure
    canvas = cls.FigureCanvas(figure)
TypeError: 'NoneType' object is not callable

我发现如果我在 testImports.py 的顶部包含以下几行,它适用于所有版本的 Matplotlib 3+。

import matplotlib
matplotlib.use('qt5agg')

因此,Matplotlib 注册后端的方式似乎发生了一些变化。

在我的测试中,我还发现在 testImports.py 的顶部添加以下内容可以解决问题(也无需调用 use('qt5agg'):

import matplotlib
print(matplotlib.get_backend())

这对我来说似乎很奇怪,因为 matplotlib.get_backend() 函数只是从字典中返回一个值(来自 source):

def get_backend():
    """
    Return the name of the current backend.

    See Also
    --------
    matplotlib.use
    """
    return rcParams['backend']

这让我想到了我的问题:

    Matplotlib 3.1.0rc1 和 3.1.0rc2 之间发生了什么变化,导致必须在导入特定后端函数之前使用 matplotlib.use() 函数? 为什么调用 matplotlib.get_backend() 会极大地改变我的程序的行为,从而无需调用 matplotlib.use()

如果有帮助,我正在使用 Python 3.6.9 和 Numpy 1.18.0。

编辑: 我安装了 Matplotlib 3.2.0rc2 并且仍然遇到相同的行为。

【问题讨论】:

如果您导入from PyQt5 import QtWidgets,它也可以正常工作。 看起来像是开发版中已经修复的bug,所以在matplotlib 3.2中可能不会再出现 【参考方案1】:

我不知道我要描述的是matplotlib 的预期行为还是一个错误。也许维护者可以参与进来。

本质上,matplotlib.get_backend()NOT 只是从字典中返回一个值。事实上,它通过在钩子下导入 pyplot 触发了“后端”的延迟初始化,我可以想象会产生一些行为变化,因为仅仅调用 import matplotlib 不足以触发初始化(至少在所有例)。

当您导入 matplotlib 或其任何模块时,matplotlib.__init__.py 会隐式运行。 如果键值设置为rcsetup._auto_backend_sentinelrcParams.__getitem__() 似乎打算对“后端”进行延迟初始化。

当第一次构造 rcParam 对象时,dict 中没有“后端”键,即使所需的默认值(可能取决于 matplotlibrc?)是rcsetup._auto_backend_sentinel

__setitem__() 方法的这些行可能涉及key == backendval == rcsetup._auto_backend_sentinel,但backend 还不是rcParams(MutableMapping, dict) 对象中的有效键的情况。因此,当第 3 行 if 'backend' in self:(隐式调用 __getitem__ 并使用 'backend' 键检查该键是否存在)被处理时,不会发生后端的延迟初始化,因为我标记为“THIS MAY THROW”的__getitem__ 行实际上确实会抛出,并防止随后的延迟初始化。

# Excerpt of rcParams(MutableMapping, dict).__setitem__ from around lines 672
elif key == 'backend':
    if val is rcsetup._auto_backend_sentinel:
        if 'backend' in self:
            return
# Excerpt of rcParams(MutableMapping, dict).__getitem__ from around lines 699
elif key == "backend":
    val = dict.__getitem__(self, key) # THIS MAY THROW
    if val is rcsetup._auto_backend_sentinel:
        from matplotlib import pyplot as plt
        plt.switch_backend(rcsetup._auto_backend_sentinel)

但是,在__setitem__ 中,代码确实成功地将rcParams['backend'] 设置为rcsetup._auto_backend_sentinelx in X 调用忽略 KeyErrors 是正常的)。因此rcParams 已填充,但延迟初始化尚未完成。

现在当你调用matplotlib.get_backend()(或其他相关函数)时,部分代码路径会做rcParams['backend'],这一次实际上会触发__getitem__中的延迟初始化。

【讨论】:

如果你想要一个简单的例子来确认matplotlib.get_backend() 函数不是简单地从字典中返回一个值,而是当第一次发出实际导致Qt事件循环生成并绑定到REPL循环时,除其他外,您可以在新的终端会话中运行以下命令:import matplotlib as mplfrom PyQt5.QtWidgets import QApplication;并确认QApplication.instance() is None。现在“简单地”使用 mpl.rcParams['backend'] 查看后端。如果再次检查,您会看到由于延迟初始化,Qt 实例正在运行。

以上是关于在 Matplotlib 3.1.2 中使用 qt5agg 后端,get_backend() 会改变行为的主要内容,如果未能解决你的问题,请参考以下文章

在同一进程中将 matplotlib 与 Qt5 后端一起使用并运行现有 QApplication

使用 Qt Designer 表单和 PyQt5 在 QWidget 中绘制 matplotlib 图形

Matplotlib 事件处理:他们何时发送 matplotlib 事件以及何时发送 Qt 事件

用于 Qt 4、Python 3 的 matplotlib 小部件

Qt5 应用程序中的 matplotlib RectangleSelector 图形故障

Matplotlib植入PyQt5 + QT5的UI呈现