• 面向对象的三大特性之继承


    一 继承的概念

    1、什么是继承

    继承是一种创建新类的的方式,新建的类可以称为子类或派生类,被继承的类称为父类,父类又可称为基类或超类,子类会遗传父类的属性

    类与类之间的继承指的是什么’是’什么的关系(比如人类,猪类,猴类都是动物类)

    要找出类与类之间的继承关系,需要先抽象,再继承。抽象即总结相似之处,总结对象之间的相似之处得到类,总结类与类之间的相似之处就可以得到父类

    class Parent1(object): 
        x=1111
    
    class Sub1(Parent1): # 单继承
        pass
    
    print(Sub1.x)  #111
    

    1.1 经典类与新式类

    在 python2 中,有经典类与新式类之分:

    新式类:继承了object 类的子类, 以及该子类的子子类被称为新式类

    经典类:没有继承 object 类的子类,以及该子类的子子类被称为经典类

    在 python3 中,没有继承任何类的话,默认继承 object 类,所以 python3 中所有类都是新式类

    1.2 查看子类继承的所有父类

    通过类的内置属性__ bases__可以查看类继承的所有父类

    >> SubClass2.__bases__
    (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
    

    2、为何要用继承

    优点:用来解决类与类之间代码冗余问题

    缺点:将类耦合到了一起

    2.1 python 的多继承

    python 支持多继承,即在 python 中,新建的类可以继承一个或者多个父类。

    class Parent1(object):
        x=1111
    
    class Parent2(object):
        pass
    
    class Sub1(Parent1): # 单继承
        pass
    
    class Sub2(Parent1,Parent2): # 多继承
        pass
    
    #查看类的基类(父类)
    print(Sub1.__bases__) #(<class '__main__.Parent1'>,)
    print(Sub2.__bases__) #(<class '__main__.Parent1'>, <class '__main__.Parent2'>)
    
    print(Sub1.x)  #111
    

    优点:子类可以同时遗传多个父类的属性,最大程度地重用父类的属性

    缺点:

    ​ 1、违背了人的思维习惯:继承表达的是一种什么'是'什么的关系

    ​ 2、代码可读性会变差

    ​ 3、不建议使用多继承,有可能会引可恶的菱形问题,扩展性变差

    ​ 如果真的涉及到一个子类不可避免地要重用多个父类属性,应该使用 Mixins

    3 如何实现继承

    示例:基于继承解决类与类之间的冗余问题

    class OldboyPeople:
        school = 'OLDBOY'
    
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
    
    class Student(OldboyPeople):
        def choose_course(self):
            print('学生%s 正在选课' % self.name)
    # stu_obj = Student('lili', 18, 'female')
    # print(stu_obj.__dict__)
    # print(stu_obj.school)
    # stu_obj.choose_course()
    
    
    class Teacher(OldboyPeople):
        #           老师的空对象,'egon',18,'male',3000,10
        def __init__(self, name, age, sex, salary, level):
            # 指名道姓地跟父类OldboyPeople去要__init__
            OldboyPeople.__init__(self,name,age, sex) ##调用的是函数,因而需要传入self
            self.salary = salary
            self.level = level
    
        def score(self):
            print('老师 %s 正在给学生打分' % self.name)
    
    tea_obj=Teacher('egon',18,'male',3000,10)
    print(tea_obj.__dict__) #{'name': 'egon', 'age': 18, 'sex': 'male', 'salary': 3000, 'level': 10}
    print(tea_obj.school) #OLDBOY
    tea_obj.score() #老师 egon 正在给学生打分
    

    3.1 派生与继承

    子类继承父类,派生出自己的属性与方法,并且重用父类的属性与方法。

    解决问题:子类重用父类的属性,并派生出新的属性

      两种方式:

        1.直接引用父类的__ init__为其传参,并添加子类的属性,它是指名道姓调用某一个类下的函数,不依赖与继承关系

        2.通过super来指向父类的属性,super()调用父类提供自己的方法,严格依赖继承关系

        ps:super是一个特殊的类,super()得到一个对象,该对象指向父类的名称空间

        注意:使用哪一种都可以,但不能两种方式混合使用

    class People:
        school = '上交大'
        def __init__(self, name, sex, age):
            self.name = name
            self.sex = sex
            self.age = age
    
    #方式一:'指名道姓'地调用某一个类的函数
    class Teacher(People):
        def __init__(self, name, sex, age, title):
            People.__init__(self, name, age, sex) #'指名道姓'地调用某一个类的函数
             self.title = title
        def teach(self):
            print('%s is teaching' %self.name)
    # obj = Teacher('lili','female', 28, '高级教师') #只会找自己类中的__init__
    #
    # print(obj.teach())  #lili is teaching
                        	
    
    #方法二:super()
    '''
    调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找
    '''
    class Teacher(People):
        def __init__(self, name, sex, age, title):
            super().__init__(name, age, sex) #调用的对象的是绑定方法,自动传入self   #按照Teacher.MRO列表的从 当前类(调用 super 的类)后面的父类 往后查找
            self.title = title
        def teach(self):
            print('%s is teaching' %self.name)
    
    obj = Teacher('lili','female', 28, '高级教师') #只会找自己类中的__init__
    
    print(obj.teach())  #lili is teaching
                        
    

    注意:在python2中super 的使用需要完整地写成super(自己的类名,self),而在python3中可以简写为super()。

    这两种方法的区别是:方式一是跟继承没有关系的,它是通过类调用函数方法,而方式二的 super()是依赖继承的,并且没有直接继承关系,super()仍然会按照 MRO 继续往后查找

    关于在子类中重用父类功能的这两种方式,使用任何一种都可以,但是在最新的代码中还是推荐使用super()

    案例一:

    调用super()会得到一个特殊的对象,该对象会参照发起属性查找的那个类的mro,从当前类的父类中向后查找

    class A:
           print('from A')
            super().test()  #按照D.MRO列表,从当前类A往后查找(不包含类A)
    
    class B:
        def test(self):
            print('from B')
    
    class C(A,B):
        def test(self):
            print('from C')
    
    obj=C() 
    print(C.mro())#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
    obj.test()
    
    #结果展示
    '''
    from A
    from B  
    '''
    

    案例二:

    class A:
        def test(self):
            print('from A')
            super().test1()
    
        def test1(self):
            print('from AAA')
    class B:
        def test(self):
            print('from B')
    
    class C(A,B):
       def test1(self):
           print('from C')
    
    obj=C() #from C
    print(C.mro())#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
    obj.test()
    #结果展示
    '''
    from A
    报错:'super' object has no attribute 'test1'
    '''
    print(C.mro())#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
    obj.test()
    #结果展示
    '''
    from A
    报错:'super' object has no attribute 'test1'
    '''
    #[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
    
    '''
    报错的原因的是调用super(),它是按照调用者的.MRO列表中当前类(A)往后查A(不包含 A类),即从 B类往后找所以找不到
    '''
    

    案例三:

    class D:
        def test1(self):
            print('from D')
    
    class A:
        def test(self):
            print('from A')
            super().test1()
    
        def test1(self):
            print('from AAA')
    class B:
        def test(self):
            print('from B')
    
    class C(A,B,D):
       def test1(self):
           print('from C')
    
    obj=C()
    print(C.mro())
    obj.test()
    #结果展示
    '''
    [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class 'object'>]
    from A
    from D
    '''
    

    3.2 属性查找问题

    单继承背景下的属性查找

    示范一:

    class Foo:
        def f1(self):
            print('Foo.f1')
    
        def f2(self):
            print('Foo.f2')
            self.f1() # obj.f1()
    
    class Bar(Foo):
        def f1(self):
            print('Bar.f1')
    
    obj=Bar()
    obj.f2()
    #结果展示
    '''
    Foo.f2
    Bar.f1
    '''
    

    示范二:想要调用的当前类的属性

    class Foo:
        def f1(self):
            print('Foo.f1')
    
        def f2(self):
            print('Foo.f2')
            Foo.f1(self) # 调用当前类中的f1
    
    class Bar(Foo):
        def f1(self):
            print('Bar.f1')
    
    obj=Bar()
    obj.f2()
    #结果展示
    '''
    Foo.f2
    Foo.f1
    '''
    

    父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方法将方法设置为私有的

    示范三:

    class Foo:
        def __f1(self): # _Foo__f1
            print('Foo.f1')
    
        def f2(self):
            print('Foo.f2')
            self.__f1() # self._Foo__f1,# 调用当前类中的f1
    
    class Bar(Foo):
        def __f1(self): # _Bar__f1
            print('Bar.f1')
    
    obj=Bar()
    obj.f2()
    
    #结果展示
    '''
    Foo.f2
    Foo.f1
    '''
    

    4、多继承的菱形问题与属性查找

    4.1 菱形问题介绍

    大多数面向对象 1 都不支持多继承,在 python 中,一个子类时可以同时继承多个父类的,这固然可以带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发菱形问题(或称钻石问题),菱形结构如下:

    v2-7c6088a106bbf1fc46de0cca9c03a037_1440w

    #A类在顶部,B类和C类分别位于其下方,D类在底部将两者连接在一起形成菱形。
    
    #注菱形继承(最终继承的汇集点不是 object)
    #非菱形继承(最终继承的汇集点是 object)
    

    示例一:这不是菱形继承

    class A(object):
        pass
    
    
    class B(object):
        pass
    
    
    class C(A, B):
        pass
      
     #这不菱形继承,最终汇集点为 object
    

    示例二:菱形继承

    class D(object):
        pass
    
    class A(D):
        pass
    
    
    class B(D):
        pass
    
    
    class C(A, B):
        pass
      
    #这是菱形继承,最终汇集点为 类D
    

    4.2 继承原理

    对于定义的每一个类,python 都会计算出一个方法解析顺序(MRO)列表,该 MRO 列表就是一个简单的所有基类的线性顺序列表,如下:

    class A(object):
        def test(self):
            print('from A')
    
    
    class B(A):
        def test(self):
            print('from B')
    
    
    class C(A):
        def test(self):
            print('from C')
    
    
    class D(B,C):
        pass
    
    
    obj = D()
    obj.test() # 结果为:from B
    
    print(D.mro()) # [<class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
    

    python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

    由类发起的属性查找准则:

    1.子类会先于父类被检查
    2.多个父类会根据它们在列表中的顺序被检查
    

    由对象发起的属性查找准则:

    1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去,
    2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去,
    

    4.3 深度优先和广度优先

    1 非菱形继承下的属性查找

    如果多继承是非菱形继承,经典类与新式的属性查找顺序一样:

    都是按照 MRO列表中顺序来查找

    image-20200409185422400

    class E:
        def test(self):
            print('from E')
    
    
    class F:
        def test(self):
            print('from F')
    
    
    class B(E):
        def test(self):
            #print('from B')
            pass
    
    
    class C(F):
        def test(self):
            print('from C')
    
    
    class D:
        def test(self):
            print('from D')
    
    
    class A(B, C, D):
        # def test(self):
        #     print('from A')
        pass
    
    
    print(A.mro())
    '''
    [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class 'object'>]
    '''
    
    obj = A()
    obj.test() # 结果为:from E
    # 可依次注释上述类中的方法test来进行验证
    

    2 菱形继承下的属性查找

    经典类:深度优先,会在检索第一条分支的时候就直接一条道走到黑,即会检索大脑袋(共同的父类),此时无 object 类

    ​ 注:在 python2 中,未继承 object 的类及子类,都是经典类

    属性查找顺序如图所示:

    image-20200409185805284

    当类是经典类时,多继承情况下,在要查找属性不存在时 ,会按照深度优先的凡是查找下去

    示例一:经典类

    class G: # 在python2中,未继承object的类及其子类,都是经典类
        pass
    
    class E(G):
        pass
    
    class F(G):
        def test(self):
            print('from F')
    
    class B(E):
        pass
    
    class C(F):
        def test(self):
            print('from C')
    
    class D(G):
        def test(self):
            print('from D')
    
    class A(B,C,D):
        pass
     
    
    # 经典类:A->B->E->G->C->F->D
    obj = A()
    obj.test() # from C
    

    ​ 新式类:广度优先,会在检索最后一条分支的时候检索大脑袋

    属性查找顺序如下图:

    image-20200409190005443

    当类是新式类,多继承情况下,在要查找属性不存在时,会按照广度优先的方式下查找下去

    示例二:新式类

    class G(object):
        def test(self):
            print('from G')
    
    class E(G):
        def test(self):
            print('from E')
    
    class F(G):
        def test(self):
            print('from F')
    
    class B(E):
        def test(self):
            print('from B')
    
    class C(F):
        def test(self):
            print('from C')
    
    class D(G):
        def test(self):
            print('from D')
    
    class A(B,C,D):
        # def test(self):
        #     print('from A')
        pass
    
    obj = A()
    obj.test() # 如上图,查找顺序为:obj->A->B->E->C->F->D->G->object
    # 可依次注释上述类中的方法test来进行验证
    

    总结

    多继承要用时,需要规避以下几个问题

    1、继承结构尽量不要过于繁杂

    2、推荐使用 mixins 机制:在多继承的背景下满足继承的什么'是'什么关系

    二 mixins 机制

    mixins机制核心:就是在多继承背景下尽可能地提升多继承的可读性

    ps:让多继承满足人的思维习惯==》什么'是'什么关系

    举例说明:

    ​ python 提供了 MIxins 机制,简单来说, MIxins机制指的是子类混合(mixin)不同类的功能,这些类 1 采用统一的命名规范(例如 MIxin后缀),以此表示这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的(该类不是作为父类,只是提供了一种功能方法)

    class Vehicle:  # 交通工具  #父类
        pass
    
    
    class FlyableMixin:  #定义功能类
        def fly(self):
            '''
            飞行功能相应的代码        
            '''
            print("I am flying")
    
    
    class CivilAircraft(FlyableMixin, Vehicle):  # 民航飞机
        pass
    
    
    class Helicopter(FlyableMixin, Vehicle):  # 直升飞机
        pass
    
    
    class Car(Vehicle):  # 汽车
        pass
    
    # ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路
    

    可以看到,上面的CivilAircraft、Helicopter类实现了多继承,不过它继承的第一个类我们起名为FlyableMixin,它是为了会告诉后来读代码的人,这个类是一个Mixin类,表示混入(mix-in),这种命名方式就是用来明确地告诉别人(python语言惯用的手法),这个类是作为功能添加到子类中,而不是作为父类,它的作用同Java中的接口。所以从含义上理解,CivilAircraft、Helicopter类都只是一个Vehicle(交通工具),而不是一个飞行器。

    使用 MIxin 类实现多继承注意事项:

    1、首先它必须表示一个功能而非某个物品,python 对于 mixin类的命名方式一般以 Mixin,able,ible 为后缀

    2、它的功能或者职责必须单一,如果需要多个功能,那就多定义些 Mixin 类,一个类可以继承多个 Mixin,为了保证遵循继承的'is-a'原则,只能继承一个标识其归属含义的父类

    3、它不依赖于子类的实现

    4、子类即便没有继承这个 Mixin 类,也可以正常工作,只是缺少了该部分功能

    Ps:Mixins是从多个类中重用代码的好方法,但是需要付出相应的代价,我们定义的Minx类越多,子类的代码可读性就会越差。

  • 相关阅读:
    Python基础编程常用模块汇总
    博客目录
    网络编程
    python 对象
    python模块和规范开发
    python常用内置函数
    python递归函数和匿名函数
    python装饰器
    python迭代器,生成器,推导式
    python作用域
  • 原文地址:https://www.cnblogs.com/xy-han/p/12669757.html
Copyright © 2020-2023  润新知