• 20.面向对象:继承应用(在子类派生重用父类功能(super))。继承实现原理(继承顺序、菱形问题、继承原理、Mixins机制)。组合


    • 引子
    • 继承应用

    • 在子类派生的新方法中重用父类的功能(super)

    • 继承实现原理

    • 继承顺序

    • 菱形问题

    • 继承原理

    • Mixins机制

    • 组合


    • 继承应用

      类与类之间的继承指的是什么’是’什么的关系(比如人类,猪类,猴类都是动物类)。子类可以继承/遗传父类所有的属性,因而继承可以用来解决类与类之间的代码重用性问题。比如我们按照定义Student类的方式再定义一个Teacher类

    class Student:           # 定义学生类
        school = "虹桥校区"   # 冗余共同属性
    
        def __init__(self,name,age,gender):  # 学生类与老师类有冗余部分
            self.name = name
            self.age = age
            self.gender = gender
    
        def choose(self):
            print("%s 选课成功" %self.name)
    
    stu1 = Student("jack",18,"male")
    stu2 = Student("tom",19,"male")
    stu3 = Student('lili',29,"female")
    
    class Teacher:          # 定义老师类
        school = "虹桥校区"
    
        def __init__(self,name,age,gender,level):  # 老师类与学生类功能多一个level
            self.name = name
            self.age = age
            self.gender = gender
            self.level = level
    
        def score(self):
            print("%s 正在为学生打分" %self.name)
    
    tea1 = Teacher('egon',18,"male",10)
    tea2 = Teacher('lxx',38,"male",3)
    
    
    从上面看出:类Teacher与Student之间存在重复的代码,老师与学生都是人类,所以我们可以得出如下继承关系,实现代码重用
    • 在子类派生的新方法中重用父类的功能:

    • 方式一: 指名道姓地引用某一个类的函数,与继承无关

    class People:
        school = "虹桥校区"    # 将冗余数据放到父类当中
    
        def __init__(self,name,age,gender):  # 将学生类和老师类的共同属性放到父类中间解决冗余问题
            self.name = name
            self.age = age
            self.gender = gender
    
    class Student(People):
        def choose(self):
            print("%s 选课成功" %self.name)
    
    class Teacher(People):
             #      定义一个__init__函数是形参  
    	 #      空对象,'egon',18,"male",10
        def __init__(self,name,age,gender,level):  # 老师类多一个level就需要保留
       # 调用父类的__init__是函数(实参)不是方法,为老师类的__init__进行传参
            People.__init__(self,name,age,gender)  # 在子类的派生当中重用父类功能
    
            self.level = level
    
        def score(self):
            print("%s 正在为学生打分" %self.name)
    
    
    stu1 = Student("jack",18,"male")
    stu2 = Student("tom",19,"male")
    stu3 = Student('lili',29,"female")
    
    
    tea1 = Teacher('egon',18,"male",10)  # 空对象,'egon',18,"male",10
    tea2 = Teacher('lxx',38,"male",3)
    
    
    # print(stu1.school)
    # print(stu1.name)
    # print(stu1.age)
    # print(stu1.gender)
    print(tea1.__dict__)
    
    • 方式二: super()返回一个特殊的对象,该对象会参考发起属性查找的那一个类的mro列表,去当前类的父类中找属性,严格依赖继承

    class People:
        school = "虹桥校区"
    
        def __init__(self,name,age,gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    class Teacher(People):
        #            空对象,'egon',18,"male",10
        def __init__(self,name,age,gender,level):
            # People.__init__(self,name,age,gender)
            super(Teacher,self).__init__(name,age,gender)
    
            self.level = level
    
        def score(self):
            print("%s 正在为学生打分" %self.name)
    
    
    tea1 = Teacher('egon',18,"male",10)  # 空对象,'egon',18,"male",10
    print(tea1.__dict__)
    
    
    # 案例:
    class A:  # [A,object]
        def test(self):
            print("from A")
            super().test()
    class B:
        def test(self):
            print('from B')
    class C(A,B):  # [C,A,B,object]
        pass
    
    # obj=C()
    # obj.test()
    
    obj1 = A()
    obj1.test()
    
    • 继承的实现原理

      继承顺序

      Python中子类可以同时继承多个父类,如A(B,C,D)

      如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

      如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先

    # 继承顺序
    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):
        def test(self):
            print('from D')
    
    class E(C):
        def test(self):
            print('from E')
    
    class F(D,E):
        # def test(self):
        #     print('from F')
        pass
    f1=F()
    f1.test()
    print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性
    
    # 新式类继承顺序:F->D->B->E->C->A
    # 经典类继承顺序:F->D->B->A->E->C
    # python3中统一都是新式类
    # pyhon2中才分新式类与经典类
    
    
    • 菱形问题

      大多数面向对象语言都不支持多继承,而在Python中,一个子类是可以同时继承多个父类的,这固然可以带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发著名的 Diamond problem菱形问题(或称钻石问题,有时候也被称为“死亡钻石”),菱形其实就是对下面这种继承结构的形象比喻
      菱形继承/死亡钻石:一个子类继承的多条分支最终汇聚一个非object的类上
    class G: # 在python2中,未继承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')
        pass
    
    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()  # A->B->E->C->F->D->G->object
    # print(A.mro())
    
    obj.test()
    
    • 继承原理

    • python到底是如何实现继承的呢? 对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表,如下

    A.mro()  # 等同于A.__mro__
    [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
    
    • 为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。 而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

      • 1.子类会先于父类被检查
        2.多个父类会根据它们在列表中的顺序被检查
        3.如果对下一个类存在两个合法的选择,选择第一个父类
    • Mixins机制

      继承表达的是一个is-a的关系(什么是什么的关系)

      单继承可以很好的表达人类的逻辑(一个子类继承一个父类符合逻辑且清晰)
      多继承就有点乱,在人类的世界观里,一个物品不可能是多种不同的东西,因此多重继承
      在人类的世界观里是说不通的,它仅仅只是代码层面的逻辑(还可能导致菱形问题)
      民航飞机、直升飞机、轿车都是一个(is-a)交通工具,前两者都有一个功能是飞行fly,但是轿车没有,所以如下所示
    # 我们把飞行功能放到交通工具这个父类中是不合理的
    class Vehicle:  # 交通工具
        def fly(self):
            '''
            飞行功能相应的代码        
            '''
            print("I am flying")
    
    class CivilAircraft(Vehicle):  # 民航飞机
        pass
    
    class Helicopter(Vehicle):  # 直升飞机
        pass
    
    class Car(Vehicle):  # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
        pass
    
    Python语言没有接口功能,但Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系,如下
    class Vehicle:   # 交通工具
        pass
    
    class FlyableMixin:     # 定义一个飞行功能
        def fly(self):
            print('flying')
    
    class CivilAircraft(FlyableMixin,Vehicle):  # 民航飞机  继承了Flyablemixin就使用它的功能
        pass
    
    class Helicopter(FlyableMixin,Vehicle):    # 直升飞机
        pass
    
    class Car(Vehicle):             # 小汽车   不继承就不使用它的功能,也不影响
        pass
    
    # ps: 采用这种编码规范(如命名规范)来解决具体的问题是python惯用的套路,大家都照这种规范来
    # 这种规范叫什么呢?
    # 建议不要用多继承,如果要用到这个继承:把用来表达归属关系的那个类(当然也是最复杂的
    # 那个类)往多继承的最右边去放(Vehicle)。把用来添加功能的类往左边放并改名Mixin为后缀
    # 的名字(FlyableMixin)这种类的特点是这种类里面就只放功能,并且功能的特点是能独立运行
    
    总结:可以看到,上面的CivilAircraft、Helicopter类实现了多继承,不过它继承的第一个类我们起名
    FlyableMixin,而不是Flyable,这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类
    单词表示混入(mix-in),这个名字给人一种提示性的效果,只要以Mixin这种命名方式的类就知道这个类只
    是为我这个类来添加功能的,只要继承这个类就说明混合了这个功能
    • 组合

      组合:一个对象的属性值是指向另外一个类的对象,称为类的组合

      组合与继承都是用来解决代码的重用问题的,不同的是:

      继承是一种什么“是”什么的关系
      组合是则一种“有”什么的关系
      比如学校有学生,老师,学生有多门课程,课程有:类别、周期、学费。学生选python课程或linux课程,或多门课程,应该使用组合,如下示例
    class People:
        school = "虹桥校区"
    
        def __init__(self,name,age,gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    class Student(People):
        def choose(self):
            print("%s 选课成功" %self.name)
            
    class Teacher(People):
        #            空对象,'egon',18,"male",10
        def __init__(self,name,age,gender,level):
            People.__init__(self,name,age,gender)
    
            self.level = level
    
        def score(self):
            print("%s 正在为学生打分" %self.name)
    
    class Course:
        def __init__(self,name,price,period):
            self.name = name
            self.price = price
            self.period =period
    
        def tell(self):
            print('课程信息<%s:%s:%s>' %(self.name,self.price,self.period))
            
    # 课程属性
    python = Course("python全栈开发",19800,"6mons")
    linux = Course("linux",19000,"5mons")
    
    # 学生属性
    stu1 = Student("jack",18,"male")
    stu2 = Student("tom",19,"male")
    stu3 = Student('lili',29,"female")
    
    # 老师属性
    tea1 = Teacher('egon',18,"male",10) 
    tea2 = Teacher('lxx',38,"male",3)
    
    
    stu1.course = python   # 假如规定只有pyhton一门课程就直接等于python,这就叫组合
    stu1.course.tell()     # 直接查看课程信息
    stu1.courses = []             # 给学生1添加多门课程
    stu1.courses.append(python)   # 给学生1添加python课程
    stu1.courses.append(linux)    # 给学生1添加linux课程
    
    # 此时对象stu1集对象独有的属性、Student类中的内容、Course类中的内容于一身(都可以访问到),
    # 是一个高度整合的产物
    
    print(stu1.courses)   # 查看学生1所学的多门课程的每一门课程的信息
    for course_obj in stu1.courses:
        course_obj.tell()
    
  • 相关阅读:
    实现一个简单的ConnectionPool
    并发连接MySQL
    C#里面滥用String造成的性能问题
    String.IndexOf
    C#代码中插入X86汇编
    正确理解Handle对象
    orleans发送广播消息
    log日志方法
    PHP 批量插入数据
    逻辑漏洞小结之SRC篇
  • 原文地址:https://www.cnblogs.com/gfeng/p/14269016.html
Copyright © 2020-2023  润新知