说明
- 能够提升代码的重用率,即开发一个类,可以在多个子功能中直接使用
- 继承能够有效的进行代码的管理,当某个类有问题只要修改这个类就行,而其继承这个类的子类往往不需要就修改
继承
程序员总是想避免多次输入同样的代码。这通过创建函数来达成这个目标,但现在要解决一个更微妙的问题。如果你已经有了一个类,并要创建一个与之很像的类(可能只是新增了几个方法),该如何办呢?创建这个新类时,你不想复制旧类的代码,将其粘贴到新类中。
例如,你可能已经有了一个名为Shape的类,它知道如何将自己绘制到屏幕上。现在你想创建一个名为Rectangle的类,但它不仅知道如何将自己绘制到屏幕上,而且还知道如何计算其面积。你不想重新编写方法draw,因为Shape已经有一个这样的方法,且效果很好。那么该如何办呢?让Rectangle继承Shape的方法,使得对Rectangle对象调用方法draw时,将自动调用Shape类的这个方法。
我们说的子类扩展了超类的定义。要指定超类,可在class语句中的类名后加上超类名,并将其用圆括号括起。
class Filter:
def init(self):
self.blocked = []
def filter(self, sequence):
return [x for x in sequence if x not in self.blocked]
class SPAMFilter(Filter): # SPAMFilter是Filter的子类
def init(self): # 重写超类Filter的方法init
self.blocked = ['SPAM']
Filter是一个过滤序列的通用类。实际上,它不会过滤掉任何东西。
>>> f = Filter()
>>> f.init()
>>> f.filter([1, 2, 3])
[1, 2, 3]
Filter类的用途在于可用作其他类(如将'SPAM'从序列中过滤掉的SPAMFilter类)的基类(超类)。
>>> s = SPAMFilter()
>>> s.init()
>>> s.filter(['SPAM', 'SPAM', 'SPAM', 'SPAM', 'eggs', 'bacon', 'SPAM'])
['eggs', 'bacon']
请注意SPAMFilter类的定义中有两个要点。
第二点说明了继承很有用的原因:可以创建大量不同的过滤器类,它们都从Filter类派生而来,并且都使用已编写好的方法filter。
继承判断
要确定一个类是否是另一个类的子类,可使用内置方法issubclass。
>>> issubclass(SPAMFilter, Filter)
True
>>> issubclass(Filter, SPAMFilter)
False
如果你有一个类,并想知道它的基类,可访问其特殊属性__bases__。
>>> SPAMFilter.__bases__
(
>>> Filter.__bases__
(
同样,要确定对象是否是特定类的实例,可使用isinstance。
>>> s = SPAMFilter()
>>> isinstance(s, SPAMFilter)
True
>>> isinstance(s, Filter)
True
>>> isinstance(s, str)
False
使用isinstance通常不是良好的做法,依赖多态在任何情况下都是更好的选择。一个重要的例外情况是使用抽象基类和模块abc时
如你所见,s是SPAMFilter类的(直接)实例,但它也是Filter类的间接实例,因为SPAMFilter是Filter的子类。换而言之,所有SPAMFilter对象都是Filter对象。从前一个示例可知,isinstance也可用于类型,如字符串类型(str)。
如果你要获悉对象属于哪个类,可使用属性__class__。
>>> s.__class__
对于新式类(无论是通过使用__metaclass__ = type还是通过从object继承创建的)的实例,还可使用type(s)来获悉其所属的类。对于所有旧式类的实例,type都只是返回instance。
继承多个超(父)类
前面,你肯定注意到了一个有点奇怪的细节:复数形式的__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),它使用的算法非常复杂。所幸其效果很好,可能根本无需担心。
MRO方法搜索顺序(方法解析顺序)
python中针对类提供了一个内置属性__mro__可以查看方法搜索顺序
MRO是method resolution order,主要用于多继承时判断方法、属性的调用路径
print(C.__mro__) #输出结果
在搜索方法时,是按照__mro__的输出结果从左到右的顺序查找的
如果在当前类中找到方法,就直接执行,不再搜索
如果没有找到,就查找下个类中是否有对应的方法,如果找到,就直接执行,不再搜索
如果找到最后一个类,还没有找到方法,程序报错