• 面向对象之继承


    1、继承的概念

    继承是一种新建类的方式,新建类可以创建一个或多个父类,父类又可以称之为基类或超类,新建的类称之为子类或派生类,继承描述的是一种遗传关系,子类可以重用父类的属性和方法。使用继承可以减少类与类之间代码冗余的问题。

    在 Python 中类的继承分为单继承和多继承

    class ParentClass1():   # 定义父类
        pass
    
    class ParentClass2():   # 定义父类
        pass
    
    class SubClass1(ParentClass1):  # 定义子类继承父类, 单继承
        pass
    
    class SubClass2(ParentClass1, ParentClass2):    # Python支持多继承, 用逗号分隔开多个继承的类
        pass

    2、查看所有继承的父类:__bases__

    class ParentClass1():  
        pass
    
    class ParentClass2():  
        pass
    
    class SubClass1(ParentClass1): 
        pass
    
    class SubClass2(ParentClass1, ParentClass2):  
        pass
    
    print(SubClass1.__bases__)
    print(SubClass2.__bases__)
    print(ParentClass1.__bases__)
    print(ParentClass2.__bases__)
    
    # 运行结果
    (<class '__main__.ParentClass1'>,)
    (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
    (<class 'object'>,)
    (<class 'object'>,)
    __bases__

    如果没有指定父类,Python3 会默认继承 object 类,object 是所有类的父类

    新式类:但凡继承 object 类的子类,以及该子类的子子类....,都称之为新式类

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

    只有在 Python2 中才区分新式类和经典类,Python3 全部默认是新式类

    3、继承与抽象(先抽象后继承)

    抽象:抽取相似的部分(也就是提取一类事物的特点,范围越来越大,共性越来越少),是从大范围到小范围的过程

    继承:是基于抽象的过程,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构,是从小范围到大范围的过程

    4、派生

      1)在父类的基础上产生子类,产生的子类就叫做派生类

      2)父类中没有的方法,在子类中有,这样的方法就叫做派生方法

      3)父类中有个方法,子类中也有这个方法,就叫做方法的重写(就是把父类里的方法重写了)

    5、注意的几个概念

      1)子类可以使用父类的所有属性和方法

      2)如果子类有自己的方法,就执行自己的;如果子类没有自己的方法,就会找父类的

      3)如果子类里面没有找到,父类里也没有找到,就会报错

    6、在子类派生出新的功能中如何重用父类的功能

    现在我定义一个学生类,隶属于学校 “湫兮如风” ,学生有姓名,年龄,性别,还可以选课;再创建教师类,也隶属于学校 “湫兮如风” ,教师有姓名,年龄,性别,等级,工资,教师可以给学生打分

    class Student():
        school = '湫兮如风学院'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
        def choose_course(self):
            print('%s正在选课' %self.name)
    
    class Teacher():
        school = '湫兮如风学院'
    
        def __init__(self, name, age, gender, level, salary):
            self.name = name
            self.age = age
            self.gender = gender
            self.level = level
            self.salary = salary
    
        def score(self, stu, num):
            print('教师%s给学生%s打%s分' %(self.name, stu.name, num))
            stu.num = num
    View Code

    但是这样写有很多重复的代码,分析一下条件,教师和学生都属于学院的人,于是可以定义出一个父类让教师和学生去继承这个父类

    class People():
        school = '湫兮如风学院'
    
    class Student(People):
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
        def choose_course(self):
            print('%s正在选课' %self.name)
    
    class Teacher(People):
    
        def __init__(self, name, age, gender, level, salary):
            self.name = name
            self.age = age
            self.gender = gender
            self.level = level
            self.salary = salary
    
        def score(self, stu, num):
            print('教师%s给学生%s打%s分' %(self.name, stu.name, num))
            stu.num = num
    View Code

    这样的处理还不够彻底,因为还有 __init__ 里面还有部分重复的代码,可以将这些重复的代码放入父类,但是教师类中还有等级和工资无法处理,这两个对于父类来说就是多出来的,会报错

    class People():
        school = '湫兮如风学院'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    class Student(People):
    
        def choose_course(self):
            print('%s正在选课' %self.name)
    
    class Teacher(People):
    
        def score(self, stu, num):
            print('教师%s给学生%s打%s分' %(self.name, stu.name, num))
            stu.num = num
    
    stu = Student('qiu', 22, 'male')
    tea = Teacher('xi', 20, 'male', 10, 3000)
    
    # 运行
    TypeError: __init__() takes 4 positional arguments but 6 were given
    View Code

    可以在教师类中派生新的 __init__ ,将教师的姓名,年龄,性别,等级,工资写入,但这又回到了上一步,又有了重复的代码,这就引入了需要解决的问题:在子类派生出新的功能中如何重用父类的功能?

    在函数中我们介绍了调用的概念,在父类中有这个功能,在子类中不想再写,就可以调用这个功能

    class People():
        school = '湫兮如风学院'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    class Student(People):
    
        def choose_course(self):
            print('%s正在选课' %self.name)
    
    class Teacher(People):
    
        def __init__(self, name, age, gender, level, salary):
    
            # 注意: 这里不能使用self去调用__init__, 这样调用的永远是自己的__init__,
            # 因为这里的self是Teacher的一个对象, 就是tea, tea先从自己去找__init__, 没有找到
            # 再到Teacher中找__init__, 找到了, 于是永远在调用自己的__init__, 无法访问到父类的__init__
            # 于是用类名去调, 这实际上就是一个普通函数, 没有自动传值的功能, 需要将参数一个不少的传入
            People.__init__(self, name, age, gender)
            self.level = level
            self.salary = salary
    
        def score(self, stu, num):
            print('教师%s给学生%s打%s分' %(self.name, stu.name, num))
            stu.num = num
    
    stu = Student('qiu', 22, 'male')
    tea = Teacher('xi', 20, 'male', 10, 3000)
    View Code

    这是第一种方式,指名道姓的访问某一个类中的函数,这实际上与面向对象无关,即便是 Teacher 和 Person 没有继承关系,也能够调用

    在讲第二种方式之前,需要先了解在继承背景下,经典类与新式类的属性查找顺序

    首先是单继承,在单继承背景下,无论是新式类还是经典类属性查找顺序都一样,都是先从对象中查找,对象中没有到类中,类中没有再到父类中。

    而在多继承背景下,如果一个子类继承多个父类,但这些父类没有再继承同一个非 object 的父类,在这种情况下,新式类还是经典类属性查找顺序也是一样的,先在对象中查找,对象中没有再到对象所在的类中,再没有就会按照从左到右的顺序一个父类一个父类的找下去

    所以这里的查找顺序是:obj ---> A ---> B ---> E ---> C ---> F ---> D,D中没有再到 object 中查找,在这里 object 不在讨论范围之内,比如要查找 name 属性,object类 中不可能会去定义一个 name 属性。下面是代码实现

    class E():
        x = 'E'
        pass
    
    class F():
        X = 'F'
        pass
    
    class B(E):
        # x = 'B'
        pass
    
    class C(F):
        x = 'C'
        pass
    
    class D():
        x = 'D'
        pass
    
    class A(B, C, D):
        # x = 'A'
        pass
    
    obj = A()
    print(obj.x)
    View Code

    还有一种情况,被称之为 “菱形继承” ,可以抽象的想像到,最终的父类是汇聚到一点,继承了同一个父类,并且这个父类也不是 object 类。在这种情况下,新式类和经典类的属性查找顺序不同。

    在新式类中,是按照广度优先查找,即:obj ---> A ---> B ---> E ---> C ---> F ---> D ---> G

    在经典类中,是按照深度优先查找,即:obj ---> A ---> B ---> E ---> G ---> C ---> F ---> D

    对于定义的每一个类,Python 会计算出一个方法解析顺序(MRO)列表,这个 MRO 列表就是用于保存继承顺序的一个列表

    class G():
        x = 'G'
        pass
    
    class E(G):
        x = 'E'
        pass
    
    class F(G):
        X = 'F'
        pass
    
    class B(E):
        x = 'B'
        pass
    
    class C(F):
        x = 'C'
        pass
    
    class D(G):
        x = 'D'
        pass
    
    class A(B, C, D):
        x = 'A'
        pass
    
    obj = A()
    # print(obj.x)
    print(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'>]
    新式类查找顺序及MRO

    为了实现继承,Python 会在 MRO 列表上从左到右开始查找父类,直到找到第一个匹配这个属性的类为止,而这个 MRO 列表的构造是通过一个 C3 线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的 MRO 列表并遵循如下三条准则:

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

      2)如果有多个父类,则根据继承语法在列表内的书写顺序被检查

      3)如果多个类继承了同一个父类,则子类中只会选取继承语法括号中的第一个父类

    了解了以上知识点,现在继续来说在子类派生出的新功能中如何重用父类功能的方式二

    有一个叫做 super 的内置函数,调用该函数会得到一个特殊的对象,该对象是专门用来访问父类中属性和方法

    class People():
        school = '湫兮如风学院'
    
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    class Student(People):
    
        def choose_course(self):
            print('%s正在选课' %self.name)
    
    class Teacher(People):
    
        def __init__(self, name, age, gender, level, salary):
    
            super(Teacher, self).__init__(name, age, gender)
            self.level = level
            self.salary = salary
    
        def score(self, stu, num):
            print('教师%s给学生%s打%s分' %(self.name, stu.name, num))
            stu.num = num
    
    stu = Student('qiu', 22, 'male')
    tea = Teacher('xi', 20, 'male', 10, 3000)
    super

    强调:super 会严格参照类的 MRO 列表依次查找属性

    这是子类在类里面调用父类方法,使用 super(子类类名,self).方法名() 或 super().__init__(参数),在 Python3 中 super 可以不传参数,Python2 中必须要传参数

      

  • 相关阅读:
    EPPlus实战篇——Excel写入
    EPPlus实战篇——Excel读取
    Elasticsearch,Kibana,Logstash,NLog实现ASP.NET Core 分布式日志系统
    ElasticSearch+NLog+Elmah实现Asp.Net分布式日志管理
    C盘清理
    vs2017离线安装且安装包不占用C盘空间
    CommandLine exe参数
    Nuget 打包 for .Net Standart project
    Redis 应用:缓存
    Redis架构设计
  • 原文地址:https://www.cnblogs.com/qiuxirufeng/p/9839106.html
Copyright © 2020-2023  润新知