python基础(面向对象(超(父)类、子类继承)、继承判断、MRO方法解析顺序)

为啥要继承

python基础(面向对象(超(父)类、子类继承)、继承判断、MRO方法解析顺序)python基础(面向对象(超(父)类、子类继承)、继承判断、MRO方法解析顺序)

说明

  1. 能够提升代码的重用率,即开发一个类,可以在多个子功能中直接使用
  2. 继承能够有效的进行代码的管理,当某个类有问题只要修改这个类就行,而其继承这个类的子类往往不需要就修改

继承

程序员总是想避免多次输入同样的代码。这通过创建函数来达成这个目标,但现在要解决一个更微妙的问题。如果你已经有了一个类,并要创建一个与之很像的类(可能只是新增了几个方法),该如何办呢?创建这个新类时,你不想复制旧类的代码,将其粘贴到新类中。
例如,你可能已经有了一个名为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类中方法init的定义。
  •  直接从Filter类继承了方法filter的定义,因此无需重新编写其定义。

第二点说明了继承很有用的原因:可以创建大量不同的过滤器类,它们都从Filter类派生而来,并且都使用已编写好的方法filter。

 

继承判断

要确定一个类是否是另一个类的子类,可使用内置方法issubclass。
>>> issubclass(SPAMFilter, Filter) 
True 
>>> issubclass(Filter, SPAMFilter) 
False 
如果你有一个类,并想知道它的基类,可访问其特殊属性__bases__。
>>> SPAMFilter.__bases__ 
(<class __main__.Filter at 0x171e40>,) 
>>> Filter.__bases__ 
(<class 'object'>,) 
同样,要确定对象是否是特定类的实例,可使用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__ 
<class __main__.SPAMFilter at 0x1707c0> 

对于新式类(无论是通过使用__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__)   #输出结果<class '__main__.C'>,<class '__main__.B'>,<class '__main__.A'><class 'object'>

在搜索方法时,是按照__mro__的输出结果从左到右的顺序查找的

如果在当前类中找到方法,就直接执行,不再搜索

如果没有找到,就查找下个类中是否有对应的方法,如果找到,就直接执行,不再搜索

如果找到最后一个类,还没有找到方法,程序报错