【问题标题】:Prevent matplotlib statefulness防止 matplotlib 有状态
【发布时间】:2015-02-20 14:48:58
【问题描述】:

如果我在matplotlib 中创建一个Axes 对象并对其进行变异(即通过绘制一些数据)然后我调用一个函数而不将我的Axes 对象传递给该函数函数仍然可以改变我的Axes。例如:

import matplotlib.pyplot as plt
import numpy as np

def innocent_looking_function():
    #let's draw a red line on some unsuspecting Axes!
    plt.plot(100*np.random.rand(20), color='r')

fig, ax = plt.subplots()
ax.plot(100*np.random.rand(20), color='b') #draw blue line on ax
#ax now has a blue line, as expected

innocent_looking_function()
#ax now unexpectedly has a blue line and a red line!

我的问题是:我可以防止这种全局变量行为吗?我知道我可以在调用任何 innocent_looking_function() 之前先调用 plt.close(),但是有什么方法可以将其设为默认值吗?

【问题讨论】:

  • 但这真的有意义吗? plt.plot 只使用当前活动的轴和绘图。如果您想避免第三方代码绘制到您的图形,您可以例如关闭它。使用sca 取消设置您创建的轴,因为currently active 轴也应该可以工作。在我看来,强制plt.plot 关闭所有打开的地块的默认行为可能很难激发:)
  • 我完全理解您在这里的担忧,但我认为 python 代码与其他 OO 语言有点不同,例如Java/C#。在 python 中,信息隐藏是调用者和被调用者之间的约定。例如。与许多其他语言一样,没有私有成员属性,但属性以下划线开头的约定应视为private。但这不是强制执行的。我认为你的例子是类似的情况。我会考虑无条件地打电话给plt.plot,但不以任何方式禁止。
  • 很好。这并不能真正帮助我回答我的问题,你基本上是在告诉我我不应该担心它:)
  • 不,我是说:我认为没有简单直接的解决方案。但当然,这可能是一个问题,这是有正当理由的。但是对于这些情况,可能有一个简单的解决方法:如果您确定 plt.gca() 不会返回您的轴,那么其他函数应该不能再操纵您的绘图了。还有sca()...

标签: python matplotlib


【解决方案1】:

当然!您需要做的是在您制作图时完全绕过pyplot 状态机。

它更冗长,因为您不能只调用fig = plt.figure()


首先,让我解释一下plt.gca()plt.gcf() 的工作原理。使用pyplot 接口时,matplotlib 存储所有已创建但未显示的图形管理器。图形管理器基本上是图形的 gui 包装器。

plt._pylab_helpers.Gcf 是存储图形管理器并跟踪当前处于活动状态的图形管理器的单例对象。 plt.gcf()_pylab_helpers.Gcf 返回活动图窗。每个Figure 对象都会跟踪它自己的轴,所以plt.gca() 就是plt.gcf().gca()

通常,当您拨打plt.figure()时,它:

  1. 创建返回的图形对象
  2. 使用适当的后端为该图创建一个FigureManager
  3. 图形管理器创建FigureCanvas、gui 窗口(根据需要)和NavigationToolbar2(缩放按钮等)
  4. 然后将图形管理器实例添加到 _pylab_helpers.Gcf 的图形列表中。

这是我们要绕过的最后一步。


这是一个使用非交互式后端的快速示例。请注意,因为我们不担心与情节交互,我们可以跳过整个图形管理器,只创建一个 FigureFigureCanvas 实例。 (从技术上讲,我们可以跳过FigureCanvas,但只要我们想将绘图保存到图像等,就需要它。)

import matplotlib.backends.backend_agg as backend
from matplotlib.figure import Figure

# The pylab figure manager will be bypassed in this instance. `plt.gca()`
# can't access the axes created here.
fig = Figure()
canvas = backend.FigureCanvas(fig)
ax = fig.add_subplot(111)

只是为了证明gca 看不到这个坐标轴:

import matplotlib.pyplot as plt
import matplotlib.backends.backend_agg as backend
from matplotlib.figure import Figure

# Independent figure/axes
fig = Figure()
canvas = backend.FigureCanvas(fig)
ax = fig.add_subplot(111)
ax.plot(range(10))

# gca() is completely unaware of this axes and will create a new one instead:
ax2 = plt.gca()
print 'Same axes?:', id(ax) == id(ax2)

# And `plt.show()` would show the blank axes of `ax2`

有了交互式支持,它会变得更加复杂。不能调用plt.show(),所以需要自己启动gui的mainloop。您可以“从头开始”完成所有操作(参见任何“嵌入 matplotlib”示例),但 FigureManager 将支持的特定部分抽象掉:

以使用 TkAgg 后端为例:

import matplotlib.backends.backend_tkagg as backend
from matplotlib.figure import Figure

fig = Figure()
ax = fig.add_subplot(111)

manager = backend.new_figure_manager_given_figure(1, fig)
manager.show()
backend.show.mainloop()

要使用其他后端之一,只需更改后端导入即可。例如,对于 Qt4:

import matplotlib.backends.backend_qt4agg as backend
from matplotlib.figure import Figure

fig = Figure()
ax = fig.add_subplot(111)

manager = backend.new_figure_manager_given_figure(1, fig)
manager.show()
backend.show.mainloop()

这实际上甚至适用于 IPython 笔记本中使用的 nbagg 后端。只需将后端导入更改为import matplotlib.backends.backend_nbagg as backend

【讨论】:

  • 太棒了!有什么方法可以在 IPython Notebook 之类的东西中使用这种方法?我将如何显示fig
  • 使用交互式图形会更加复杂且特定于后端。我现在正在研究这些例子。不过,我认为使用 ipython 笔记本可能无法实现。 (我几乎从不使用笔记本,所以我对它们不是很熟悉。)我假设 IPython 使用 pyplot 的图形管理器来查找您创建的图形,我不知道如何告诉 IPython 使用图形管理器这是手动创建的。
  • @nicolaskruchten - 忽略它不能与 IPython 笔记本一起使用的部分。它实际上与其他后端完全相同!
  • 谢谢@JoeKington!我正在开始打字,心烦意乱,回来发现它完成了!
猜你喜欢
  • 2015-11-06
  • 1970-01-01
  • 2015-01-18
  • 2017-03-07
  • 2020-06-15
  • 1970-01-01
  • 2012-07-14
  • 2012-01-31
  • 2011-11-04
相关资源
最近更新 更多