MEP27:将 pyplot 与后端分离#

状态#

进度

分支和 Pull requests#

主 PR(包括 GTK3):

后端特定分支差异:

摘要#

此 MEP 重构了后端,以提供更结构化和一致的 API,删除通用代码并整合现有代码.为此,我们建议拆分:

  1. FigureManagerBase 及其派生类到核心功能类 FigureManager 和后端特定类 WindowBase ,以及

  2. ShowBase 及其派生类到 Gcf.show_allMainLoopBase .

详细描述#

此 MEP 旨在将后端 API 合并为一个统一的 API,从后端中删除通用代码(包括 _pylab_helpersGcf ),并将代码推送到 matplotlib 中更合适的级别.这样,我们就会自动消除后端中出现的不一致之处,例如 FigureManagerBase.resize(w, h) ,它有时会设置画布,有时会根据后端将整个窗口设置为给定的尺寸.

通用代码主要出现在从 FigureManagerBaseShowBase 派生的类中的两个位置.

  1. 目前 FigureManagerBase 有三个工作:

    1. 文档将其描述为 pyplot 模式的辅助类,将所有内容打包成一个整洁的包

    2. 但它不仅仅包装画布和工具栏,它还完成所有窗口任务.这两个任务的合并在以下行中体现得最好: self.set_window_title("Figure %d" % num) 这将后端特定代码 self.set_window_title(title) 与 matplotlib 通用代码 title = "Figure %d" % num 结合在一起.

    3. 目前, FigureManager 的后端特定子类决定何时结束主循环.这似乎也是非常错误的,因为图形不应该控制其他图形.

  2. ShowBase 有两个工作:

    1. 它的工作是遍历在 _pylab_helpers.Gcf 中注册的所有图形管理器,并告诉它们显示自己.

    2. 其次,它的工作是执行后端特定的 mainloop 以阻止主程序,从而防止图形消失.

实施#

此 MEP 的描述为我们提供了大部分解决方案:

  1. FigureManagerBase 中删除窗口方面,让它简单地包装这个新类以及其他后端类.创建一个新的 WindowBase 类,它可以处理此功能,并具有传递方法(:arrow_right:)到 WindowBase .继承 WindowBase 的类也应该继承 GUI 特定窗口类,以确保向后兼容性( manager.window == manager.window ).

  2. ShowBase 的主循环重构为 MainLoopBase ,它也封装了循环的结束.我们将 MainLoop 的一个实例作为一个键解锁退出方法(要求在循环可以结束之前返回所有键)传递给 FigureManager .请注意,这为多个后端同时运行提供了可能性.

  3. 既然 FigureManagerBase 中没有任何后端特定的东西,那么将其重命名为 FigureManager ,并移动到一个新文件 backend_managers.py ,并注意:

    1. 这允许我们将后端的转换分解为单独的 PR,因为我们可以保持现有的 FigureManagerBase 类及其依赖项完好无损.

    2. 这也预见到了 MEP22,其中新的 NavigationBase 已经演变为独立于后端的 ToolManager .

FigureManagerBase(canvas, num)

FigureManager(figure, num)

WindowBase(title)

笔记

show

show

destroy

调用所有组件上的 destroy

destroy

full_screen_toggle

处理逻辑

set_fullscreen

resize

resize

key_press

key_press

get_window_title

get_window_title

set_window_title

set_window_title

_get_toolbar

FigureManagerBase 的所有子类的通用方法

set_default_size

add_element_to_window

ShowBase

MainLoopBase

笔记

mainloop

begin

end

当子类不再存在实例时,自动调用

__call__

方法已移动到 Gcf.show_all

未来兼容性#

正如上面在讨论 MEP 22 时所暗示的那样,此重构使得添加新的通用功能变得容易.目前,MEP 22 必须对每个从 FigureManagerBase 扩展的类进行丑陋的 hacks.有了这段代码,只需要在单个 FigureManager 类中进行修改即可.这也使得稍后弃用 NavigationToolbar2 非常简单,只需要修改单个 FigureManager

MEP 23 提供了另一个用例,其中此重构的代码将非常有用.

向后兼容性#

由于我们保持所有后端代码不变,只向现有类添加缺失的方法,因此这应该适用于所有用例.唯一的区别在于,由于 API 的标准化,那些使用 FigureManager.resize 来调整画布大小而不是窗口大小的后端.

我设想,此重构使之过时的类将被弃用,并与 NavigationToolbar2 在同一时间表上删除,另请注意,对 FigureCanvasWx 构造函数的调用签名更改,虽然向后兼容,但我认为旧的 (imho 丑陋的风格) 签名应该以与其他所有内容相同的方式被弃用和删除.

backend

manager.resize(w,h)

额外

gtk3

window

Tk

canvas

Qt

window

Wx

canvas

FigureManagerWx 有 frame 作为窗口的别名,因此这也破坏了 BC.

替代方案#

如果存在任何解决相同问题的替代方案,则应在此处讨论,并说明选择的方法的理由.

问题#

Mdehoon: 你能详细说明如何同时运行多个后端吗?

OceanWolf:@mdehoon,正如我所说,不是为了这个 MEP,但我认为这个 MEP 开启了它作为未来可能性的大门.基本上, MainLoopBase 类充当每个后端的 Gcf,在这个 MEP 中,它跟踪每个后端打开的图形数量,并管理这些后端的主循环.当它检测到没有图形仍然为该后端打开时,它会关闭后端特定的主循环.因此,我设想,只需少量调整,我们就可以实现完全多后端的 matplotlib.目前还不知道为什么有人会想要这样做,但我将这种可能性留在 MainLoopBase 中.将所有后端代码的细节从 FigureManager 中重构出来也有助于这一点,一个管理器来统治它们(后端).

Mdehoon: @OceanWolf, 好的,谢谢你的解释.对于 matplotlib 的可维护性来说,拥有统一的后端 API 非常重要.我认为这个 MEP 是朝着正确方向迈出的一步.