• 面向对象之继承


    继承的介绍

    继承是一种创建新类的方式,新建的类可以称为子类或派生类, 父类又可称为基类或超类, 子类会遗传父类的属性和方法
    需要注意的是: 在Python中, 新建的类可以继承一个或多个父类, 在Python中, 新建的类可以继承一个或多个父类。

    使用继承主要是为了解决代码冗余问题。

    案例一: 类与类之间存在冗余问题

    class Student(object):
        school = 'hnie'
    
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
        def choose_course(self):
            print('%s 正在选课' % self.name)
    
    
    class Teacher(object):
        school = 'hnie'
    
        def __init__(self, name, age, sex, salary, level):
            self.name = name
            self.age = age
            self.sex = sex
            self.salary = salary
            self.level = level
    
        def score(self):
            print('%s老师正在给学生打分' % self.name)

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

    class HniePeople(object):
        school = 'hnie'
    
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
    
    class Student(HniePeople):
    
        def choose_cource(self):
            print('%s学生正在选课' % self.name)
    
    
    class Teacher(HniePeople):
        #           老师的空对象, 'featherwit', 18, 'female', 3000, 10
        def __init__(self, name, age, sex, salary, level):
            # 指名道姓地跟父类HniePeople去要__init__
            HniePeople.__init__(self, name, age, sex)
            self.salary = salary
            self.level = level
    
        def score(self):
            print('%s老师正在给学生打分' % self.name)
    
    
    stu1 = Student('featherwit', 18, 'female')
    print(stu1.__dict__)
    print(stu1.school)
    stu1.choose_cource()
    
    tea1 = Teacher('featherwit', 18, 'female', 3000, 10)
    print(tea1.__dict__)
    print(tea1.school)
    tea1.score()

    在Python2中有经典类和新式类之分:

    • 新式类: 继承了object类的子类, 以及该子类的子类和子孙类
    • 经典类: 没有继承object类的子类, 以及该子类的子类和子孙类

    在Python3中没有继承任何类, 那么会默认继承object类, 所以Python3中所有的类都是新式类

    多继承存在的问题

    大多数面向对象语言都不支持多继承, 而在Python中, 一个子类是可以同时继承多个父类的, 这固然可以带来一个子类可以对多个不同父类加以重用的好处,
    但是也可能引发著名的Diamondproblem菱形问题(或称为钻石问题, 有时候也称为"死亡钻石"), 菱形其实就是对下面这种继承结构的比喻。

    A类在顶部, B类和C类分别位于其下方, D类在底部将两者链接在一起形成菱形

    注意: 如果两个父类都继承自object类, 那么这不算是菱形问题

    class A(object):
        def test(self):
            print('from A')
    
    
    class B(object):
        def test(self):
            print('from B')
    
    
    class C(B, A):
        def test(self):
            print('from C')

    这种继承结构下导致的问题称之为菱形问题: 如果A中有一个方法, B和/或C都重写了该方法, 而D没有重写它, 那么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, C):
        pass
    
    
    obj = D()
    obj.test()  # from B

    继承原理

    Python到底是如何实现继承的呢? 对于你定义的每个类, Python都会计算出一个方法解析顺序(MRO)列表, 该MRO列表就是一个简单的所有基类的线性顺序列表
    python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
    而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:

    1.子类会先于父类被检查

    2.多个父类会根据它们在列表中的顺序被检查
    3.如果对下一个类存在两个合法的选择,选择第一个父类
    所以obj.test()的查找顺序是, 先从对象obj本身的属性里找方法test, 没有找到, 则参照属性查找发起者(即obj)所处类D的MRO列表来依次检索, 首先在类D中未找到, 然后再B中找到方法test

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

    总结: 类相关的属性查找(类名.属性, 该类的对象.属性), 都是参照该类的mro

    类的继承特点: 深度优先与广度优先

    多继承为非菱形结构

    如果多继承是非菱形继承, Python2与Python3的属性查找顺序是一样的, 都是一个分支一个分支地找下去, 然后最后找object, 这就是深度优先

    class E(object):
        def test(self):
            print('from E')
    
    
    class F(object):
        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(object):
        def test(self):
            print('from D')
    
    
    class A(B, C, D):
        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()
    print(obj.test())

    多继承为菱形结构

    如果继承关系为菱形结果, 那么经典类与新式类会有不同的MRO, 分别对应属性的两种查找方式: 深度优先和广度优先

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


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

    多继承的Mixins机制

    Mixins机制核心: 就是在多继承背景下, 尽可能的提升多继承的可读性。也就是说让多继承满足人的思维逻辑("is-a")的问题

    Python语言可没有接口功能,但Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系,

    class Vehicle(object):
        pass
    
    
    class FlyableMixin(object):
        def fly(self):
            pass
    
    
    class CivilAircraft(FlyableMixin, Vehicle):  # 民航飞机
        pass
    
    
    class Helicopter(FlyableMixin, Vehicle):  # 直升飞机
        pass
    
    
    class Car(Vehicle):  # 汽车并不能飞, 但按照上述继承关系, 汽车也能飞
        pass

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

    使用Mixin类实现多重继承要非常小心
    1. 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
    2. 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承的“is-a”原则,只能继承一个标识其归属含义的父类
    3. 它不依赖于子类的实现
    4. 子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)

    方法的重写

    子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找,例如每个老师还有职称这一属性,我们就需要在Teacher类中定义该类自己的__init__覆盖父类的

    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): # 派生
            self.name=name
            self.sex=sex
            self.age=age
            self.title=title
        def teach(self):
    print('%s is teaching' %self.name)

    很明显子类Teacher中__init__内的前三行又是在写重复代码,若想在子类派生出的方法内重用父类的功能,有两种实现方式

    方式一:  精确的调用某一个类下的函数 -> 不依赖与继承关系

    class People:
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
        def f1(self):
            print('%s say hello!' % self.name)
    
    
    class Teacher(People):
        def __init__(self, name, age, sex, level, salary):
            People.__init__(self, name, age, sex)
    
            self.level = level
            self.salary = salary
    
    
    tea_obj = Teacher('featherwit', 18, 'man', '1', 10000)

    方式二: super()调用父类提供给自己的方法 -> 严格依赖继承关系

    class People:
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
        def f1(self):
            print('%s say hello!' % self.name)
    
    
    class Teacher(People):
        def __init__(self, name, age, sex, level, salary):
            # super(Teacher, self).__init__(name, age, self)
            super().__init__(name, age, sex)  # 调用的是方法, 自动传入参数
    
            self.level = level
            self.salary = salary
    
    
    print(Teacher.mro())
    tea_obj = Teacher('featherwit', 18, 'man', '1', 10000)
    print(tea_obj.__dict__)

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

    super()使用的小案例

    class A:
        def test(self):
            super().test()
    
    
    class B:
        def test(self):
            print('from B')
    
    
    class C(A, B):
        pass
    
    
    obj = C()
    obj.test()  # from B
    print(C.mro())

    obj.test()首先找到A下的test方法,执行super().test()会基于MRO列表(以C.mro()为准)当前所处的位置继续往后查找(),然后在B中找到了test方法并执行。

  • 相关阅读:
    日期时间類(DateTime)的应用
    C# 排版快捷鑑
    撷取指定网址中的资料Part1:WebClinet 的用法
    Chart in Web
    Android APK反编译得到Java源代码和资源文件
    iOS 6.0 GM 版全系列固件下载
    IOS判断设备是否已越狱(isJailbroken)
    批量离线下载迅雷快传资源
    Android如何防止apk程序被反编译
    Java接口学习
  • 原文地址:https://www.cnblogs.com/featherwit/p/13335894.html
Copyright © 2020-2023  润新知