多重继承能发挥积极作用。《设计模式:可复用面向对象软件的基础》一书中的适配器模式用的就是多重继承,因此使用多重继承肯定没有错(那本书中的其他 22 个设计模式都使用单继承,因此多重继承显然不是灵丹妙药)。
在 Python 标准库中,最常使用多重继承的是 collections.abc 包。这没什么问题,毕竟连 Java 都支持接口的多重继承,而抽象基类就是接口声明,只不过它可以提供具体方法的实现。5
5前面说过,Java 8 也允许提供方法实现。这个新功能在官方的 Java 教程中叫默认方法(https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html)。
在标准库中,GUI 工具包 Tkinter(tkinter 模块是 Tcl/Tk 的 Python 接口,https://docs.python.org/3/library/tkinter.html)把多重继承用到了极致。图 12-2 中展示的方法解析顺序是 Tkinter 小组件层次结构的一部分,图 12-3 则列出了 tkinter 基包中的全部小组件类(tkinter.ttk 子包中还有一些,https://docs.python.org/3/library/tkinter.ttk.html)。

图 12-3:Tkinter GUI 类层次结构的 UML 简图;使用 «mixin» 标记的类通过多重继承为其他类提供具体方法
写作本书时,Tkinter 已经 20 岁了,不能代表当下的最佳实践。但是,它却能表明当没有意识到多重继承的缺点时,程序员是如何使用多重继承的。下一节讨论一些好的做法时,会把 Tkinter 作为反面教材。
来看图 12-3 中的几个类。
❶ Toplevel:表示 Tkinter 应用程序中顶层窗口的类。
❷ Widget:窗口中所有可见对象的超类。
❸ Button:普通的按钮小组件。
❹ Entry:单行可编辑文本字段。
❺ Text:多行可编辑文本字段。
这几个类的方法解析顺序如下,这些输出使用示例 12-8 中定义的 print_mro 函数得到:
>>> import tkinter >>> print_mro(tkinter.Toplevel) Toplevel, BaseWidget, Misc, Wm, object >>> print_mro(tkinter.Widget) Widget, BaseWidget, Misc, Pack, Place, Grid, object >>> print_mro(tkinter.Button) Button, Widget, BaseWidget, Misc, Pack, Place, Grid, object >>> print_mro(tkinter.Entry) Entry, Widget, BaseWidget, Misc, Pack, Place, Grid, XView, object >>> print_mro(tkinter.Text) Text, Widget, BaseWidget, Misc, Pack, Place, Grid, XView, YView, object
在类之间的关系方面有几点要注意。
Toplevel 是所有图形类中唯一没有继承 Widget 的,因为它是顶层窗口,行为不像小组件,例如不能依附到窗口或窗体上。Toplevel 继承自 Wm,后者提供直接访问宿主窗口管理器的函数,例如设置窗口标题和配置窗口边框。
Widget 直接继承自 BaseWidget,还继承了 Pack、Place 和 Grid。后三个类是几何管理器,负责在窗口或窗体中排布小组件。各个类封装了不同的布局策略和小组件位置 API。
Button 与大多数小组件一样,只是 Widget 的子代,也间接继承 Misc,后者为各个小组件提供了大量方法。
Entry 是 Widget 和 XView 的子类,后者实现横向滚动。
Text 是 Widget、XView 和 YView 的子类,后者提供纵向滚动功能。
下面将讨论多重继承一些好的做法,看看 Tkinter 有没有践行。