MEP27: 将 pyplot 与后端解耦#

状态#

进度

分支和拉取请求#

主要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(画布, 编号)

FigureManager(图形, 编号)

WindowBase(title)

注释

显示

显示

摧毁

对所有组件调用 destroy

摧毁

full_screen_toggle

处理逻辑

set_fullscreen

调整大小

调整大小

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

注释

主循环

开始

结束

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

__call__

方法已移至 Gcf.show_all

未来兼容性#

如上文在讨论 MEP 22 时所暗示的那样,这次重构使得添加新的通用功能变得容易。目前,MEP 22 必须对每个从 FigureManagerBase 扩展的类进行丑陋的 hack。通过这段代码,这只需要在单一的 FigureManager 类中进行。这也使得稍后对 NavigationToolbar2 的弃用变得非常直接,只需要修改单一的 FigureManager 类。

MEP 23 提供了另一个使用场景,在这个场景中,重构后的代码将非常方便。

向后兼容性#

由于我们保留了所有后端代码不变,仅在现有类中添加缺失的方法,这应该可以无缝适用于所有用例。唯一的区别在于那些使用 FigureManager.resize 来调整画布而不是窗口大小的后端,这是由于API的标准化。

我设想,通过这次重构被淘汰的类会按照与 NavigationToolbar2 相同的计划被弃用和移除,同时注意到对 FigureCanvasWx 构造函数调用签名的更改,尽管向后兼容,我认为旧的(在我看来是丑陋的风格)签名应该以与其他所有内容相同的方式被弃用和移除。

后端

manager.resize(w,h)

额外

gtk3

窗口

Tk

画布

Qt

窗口

Wx

画布

FigureManagerWx 曾将 frame 作为窗口的别名,因此这也打破了向后兼容性。

替代方案#

如果有任何解决同一问题的替代方案,它们应在此处讨论,并附上所选方法的理由。

问题#

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

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

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