前一讲,你肯定注意到了一个有点奇怪的细节:复数形式的 __bases__ 。前面说过,你可使用它来获悉类的基类,而基类可能有多个。为说明如何继承多个类,下面来创建几个类。

class Calculator:
    def calculate(self, expression):
        self.value = eval(expression)

class Talker:
    def talk(self):
        print('Hi, my value is', self.value)

class TalkingCalculator(Calculator, Talker):
    pass

子类 TalkingCalculator 本身无所作为,其所有的行为都是从超类那里继承的。关键是通过从Calculator 那里继承 calculate ,并从 Talker 那里继承 talk ,它成了会说话的计算器。

>>> tc = TalkingCalculator()
>>> tc.calculate('1 + 2 * 3')
>>> tc.talk()
Hi, my value is 7

这被称为多重继承,是一个功能强大的工具。然而,除非万不得已,否则应避免使用多重继承,因为在有些情况下,它可能带来意外的“并发症”。
使用多重继承时,有一点务必注意:如果多个超类以不同的方式实现了同一个方法(即有多个同名方法),必须在 class 语句中小心排列这些超类,因为位于前面的类的方法将覆盖位于后面的类的方法。因此,在前面的示例中,如果 Calculator 类包含方法 talk ,那么这个方法将覆盖 Talker类的方法 talk (导致它不可访问)。如果像下面这样反转超类的排列顺序:

class TalkingCalculator(Talker, Calculator): pass

将导致 Talker 的方法 talk 是可以访问的。多个超类的超类相同时,查找特定方法或属性时访问超类的顺序称为方法解析顺序(MRO),它使用的算法非常复杂。所幸其效果很好,你可能根本无需担心。

>>> hasattr(tc, 'talk')
True
>>> hasattr(tc, 'fnord')
False

在上述代码中,你发现 tc (本章前面介绍的 TalkingCalculator 类的实例)包含属性 talk (指向一个方法),但没有属性 fnord 。如果你愿意,还可以检查属性 talk 是否是可调用的。

>>> callable(getattr(tc, 'talk', None))
True
>>> callable(getattr(tc, 'fnord', None))
False

请注意,这里没有在 if 语句中使用 hasattr 并直接访问属性,而是使用了 getattr (它让我能够指定属性不存在时使用的默认值,这里为 None ),然后对返回的对象调用 callable 。

setattr 与 getattr 功能相反,可用于设置对象的属性:
>>> setattr(tc, 'name', 'Mr. Gumby')
>>> tc.name
'Mr. Gumby'

要查看对象中存储的所有值,可检查其 __dict__ 属性。如果要确定对象是由什么组成的,应研究模块 inspect 。这个模块主要供高级用户创建对象浏览器(让用户能够以图形方式浏览Python对象的程序)以及其他需要这种功能的类似程序。