MEP27: 将 pyplot 与后端解耦#
状态#
进度
分支和拉取请求#
主要PR(包括GTK3):
特定后端的分支差异:
摘要#
此 MEP 重构了后端,以提供一个更结构化和一致的 API,移除通用代码并整合现有代码。为此,我们建议进行以下拆分:
FigureManagerBase及其派生类被整合到核心功能类FigureManager和后端特定类WindowBase中。ShowBase及其派生类到Gcf.show_all和MainLoopBase。
详细描述#
此 MEP 旨在将后端 API 整合为一个统一的单一 API,从后端(包括 _pylab_helpers 和 Gcf)中移除通用代码,并将代码推送到 matplotlib 中更合适的层次。通过这种方式,我们自动消除了后端中出现的不一致性,例如 FigureManagerBase.resize(w, h),它有时设置画布,有时根据后端设置整个窗口为给定的尺寸。
在从 FigureManagerBase 和 ShowBase 派生的类中,出现了两个主要的通用代码位置。
FigureManagerBase目前有 三个 任务:文档将其描述为 用于pyplot模式的辅助类,将所有内容打包成一个整洁的包
但它不仅仅包裹画布和工具栏,它还自己完成所有的窗口任务。这两个任务的合并在以下这行代码中表现得最为明显:
self.set_window_title("Figure %d" % num)这行代码将后端特定的代码self.set_window_title(title)与 matplotlib 通用代码title = "Figure %d" % num结合在一起。目前,
FigureManager的后端特定子类决定了何时结束主循环。这似乎也非常错误,因为图形不应该控制其他图形。
ShowBase有两个任务:它的任务是遍历
_pylab_helpers.Gcf中注册的所有图形管理器,并告诉它们显示自己。其次,它负责执行特定于后端的
mainloop以阻塞主程序,从而保持图形不消失。
实现#
这个 MEP 的描述给了我们大部分的解决方案:
要从
FigureManagerBase中移除窗口功能,使其仅包装这个新类以及其他后端类。创建一个新的WindowBase类来处理此功能,并通过传递方法(:arrow_right:)到WindowBase。子类化WindowBase的类也应子类化特定于GUI的窗口类,以确保向后兼容性(manager.window == manager.window)。将
ShowBase的主循环重构为MainLoopBase,它还封装了循环的结束部分。我们将MainLoop的一个实例提供给FigureManager作为一个关键来解锁退出方法(要求在循环结束前返回所有关键)。注意,这开启了多个后端同时运行的可能性。既然
FigureManagerBase中没有后端特定的内容,将其重命名为FigureManager,并移动到一个新文件backend_managers.py中,注意以下事项:这使我们能够将后端的转换分解为单独的PR,因为我们可以在保持现有的
FigureManagerBase类及其依赖关系完整的情况下进行。这也预示着 MEP22,其中新的
NavigationBase已经演变为一个与后端无关的ToolManager。
FigureManagerBase(画布, 编号) |
FigureManager(图形, 编号) |
|
注释 |
|---|---|---|---|
显示 |
显示 |
||
摧毁 |
对所有组件调用 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 曾将 |
替代方案#
如果有任何解决同一问题的替代方案,它们应在此处讨论,并附上所选方法的理由。
问题#
Mdehoon: 你能详细说明如何同时运行多个后端吗?
OceanWolf: @mdehoon,正如我所说,不是针对这个MEP,但我认为这个MEP为未来的可能性打开了大门。基本上,MainLoopBase 类作为每个后端的Gcf,在这个MEP中,它跟踪每个后端打开的图形数量,并管理这些后端的主循环。当它检测到该后端没有图形保持打开时,它会关闭后端特定的主循环。由于这一点,我想象只需稍作调整,我们就可以实现完全多后端的matplotlib。目前还不清楚为什么有人会想要这样做,但我在MainLoopBase中保留了这种可能性。通过将所有后端代码的具体细节从``FigureManager``中重构出来,也有助于这一点,一个管理者统治所有(后端)。
Mdehoon: @OceanWolf, 好的,谢谢你的解释。为后端提供统一的API对于matplotlib的可维护性非常重要。我认为这个MEP是朝着正确方向迈出的一步。