• 面向对象之继承与派生


    TOC

    一、继承与派生

    1 、继承介绍

    继承是一种新建类的方式,新建的类称之为子类或派生类,继承的父类称之为基类超类

    在python中,一个子类可以继承多个父类

    在其他语言中,一个子类只能继承一个父类

    2 、继承的作用

    减少代码的冗余

    3、如何实现继承

    • 先确认谁是子类,谁是父类
    • 在定义子类时,子类名(父类名)
    # 父类
    class Father1:
        x = 1
        pass
    
    class Father2:
        pass
    
    # 子类
    class Sub(Father1, Father2, Father3):
        pass

    3.1 如何查看父类

    子类.__bases__查看父类

    print(Sub.__bases__)  # 查看父类
    
    
    <class '__main__.Farther1'>
    
    
    print(Sub.x)
    
    1  # 此结果继承Father1中的结果

    二、寻找继承关系

    1、如何寻找继承关系:

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

    基于抽象的结果,我们就找到了继承关系

    基于上图我们可以看出类与类之间的继承关系指的是 什么“是”什么的关系。

    子类可以继承/遗传父类所有的属性,因而继承可以用来解决类与类之间的代码重用性问题。

    2、继承有什么用处

    可以减少代码的冗余

    我们可以发现上面中学生类和老师类中存在着大量的相同特征和代码,我们可以建一个老师类和学生类的父类,拥有两者相同的特征:

    # 父类-同一个学校
    class People:
        school = 'oldboy'
        country = 'China'
    
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
    
    # 教师类
    class Teacher(People):
        def change(self):
            print('老师%s正在修改分数...' % self.name)
    
    
    # 学生类
    class Students(People):
        def choice_course(self):
            print('学生%s正在选课....' % self.name)
    
    
    stu1 = Students('韩梅梅', 17, 'female')
    
    t1 = Teacher('cxk', 24, 'male')
    
    print(stu1.name, stu1.age, stu1.sex, stu1.school)
    print(t1.name, t1.age, t1.sex, t1.school)

    三、在继承背景下对象属性的查找顺序

    注意:程序的执行顺序是由上到下,父类必须定义在子类上方

    在继承背景下,对象属性的查找顺序:

    • 先从对象自己的名称空间中查找
    • 对象中没有,从子类的名称空间中查找
    • 子类中没有,从父类的名称空间中查找,若父类还是没有,就会报错
    # 父类
    class Goo:
        x = 10
        pass
    
    
    # 子类
    class Foo(Goo):
        x = 100
        pass
    
    
    foo_obj = Foo()
    foo_obj.x = 1000
    # 此时foo_obj虽然调用了FOO的类,但`对象.x`等于对foo_obj的对象进行了添加属性的操作,因此回想从对象的名称空间中查找
    print(foo_obj.x)
    
    1000
    # 父类
    class Goo:
        x = 10
        pass
    
    
    # 子类
    class Foo(Goo):
        x = 100
        pass
    
    
    foo_obj = Foo()
    # foo_obj.x = 1000
    print(foo_obj.x)
    # 当名称空间没有的时候,foo_obj会从子类中查找,子类中的x的属性为100,因此,结果是100
    
    100
    # 父类
    class Goo:
        x = 10
        pass
    
    
    # 子类
    class Foo(Goo):
        # x = 100
        pass
    
    
    foo_obj = Foo()
    # foo_obj.x = 1000
    print(foo_obj.x)
    # 此时,对象名称空间和子类名称空间中均没有x的值,因此会去父类的名称空间中查找
    
    10

    查看对象,子类,父类的名称空间

    print('对象的名称空间: ', foo_obj.__dict__)
    print('子类的名称空间: ', Foo.__dict__)
    print('父类的名称空间: ', Goo.__dict__)

    注意:

    对象添加属性的操作并不会修改子类的属性(坑)

    # 父类
    class Goo:
        x = 10
        pass
    
    
    # 子类
    class Foo(Goo):
        x = 100
        pass
    
    
    foo_obj = Foo()
    foo_obj.x = 1000
    # 此处是对对象添加属性,并不会去修改子类中x的属性
    print(foo_obj.x)

    四、派生

    1、什么是派生

    指的是子类继承父类的属性与方法,并且派生出自己独有的属性与方法

    若子类中的方法名与父类的相同,优先用子类的

    # 父类
    class Foo:
        def f1(self):
            print('from Foo.f1')
    
        def f2(self):
            print('from Foo.f2...')
            self.f1()
    
    
    # 子类
    class Bar(Foo):
        # 重写
        def f1(self):
            print('from Bar.f1...')
    
        def func(self):
            print('from Bar.func....')
    
    
    bar_obj = Bar()
    bar_obj.f1()  # 此时会先从子类中查找
    
    
    from Bar.f1...
    
    
    bar_obj.func() # 子类中存在,依旧从子类中查找
    
    
    bar_obj.f2()  # 子类中没有f2的方法,因此会去父类中查找

    2、派生后继承关系查找验证

    # 父类
    class Foo:
        def f1(self):
            print('from Foo.f1')
    
        def f2(self):
            print('from Foo.f2...')
            self.f1()
    
    
    # 子类
    class Bar(Foo):
        # 重写
        def f1(self):
            print('from Bar.f1...')
    
        def func(self):
            print('from Bar.func....')
    
    
    bar_obj = Bar()
    bar_obj.f2()
    # 对象名称空间和子类中都没有
    # 此时会去FOOl类中找,会去执行foo类中的f2,并将本身bar_obj当做第一个参数传入self,因此会打印'from Foo.f2...',之后执行self.f1()--->bar_obj.f1()
    # 而此时,bar_obj对象的名称空间中并没有,因此会去对象的子类Bar中去查找,找到之后执行 print('from Bar.func....')
    
    
    from Foo.f2...
    from Bar.f1...

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

    当我们想往对象中添加新的属性的时候,不得不再次在子类中定义__init__覆盖重写父类中的__init__,因此导致代码更加冗余

    从图中可以看出,为教师增加了薪资,为学生增加了对象

    现在每个子类中都存在着属于自己的__init__父类现在没啥用了,其实可以删掉了。

    1、两种解决办法

    两种办法都可以,但是不能混合使用

    1.1 直接引用父类的__init__

    直接引用父类的__init__为其传参,并添加子类的属性。

    class People:
        school = 'oldboy'
    
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
    
    class Teacher(People):
        def __init__(self, name, age, sex, sal):
            # 调用类内部的__init__,只是一个普通的函数,因此需要手动加上self参数
            People.__init__(self, name, age, sex)
            self.sal = sal
    
        def change_score(self):
            print('老师%s修改分数...' % self.name)
    
    
    class Student(People):
        def __init__(self, name, age, sex, girl):
            People.__init__(self, name, age, sex)
            self.girl = girl
    
        def choose_course(self):
            print('学生%s选择课程...' % self.name)
    
    
    stu1 = Student('韩', 20, 'male', '李雷')
    t1 = Teacher('tank', 30, 'male', 20000)
    print(stu1.name, stu1.age, stu1.sex, stu1.school, stu1.girl)
    print(t1.name, t1.age, t1.sex, t1.sal)

    1.2 使用super

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

    super() ---> 特殊的对象 ---> 对象.属性 ---> 父类的名称空间

    class People:
        school = 'oldboy'
    
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
    
    class Teacher(People):
        def __init__(self, name, age, sex, sal):
            # 和第一种方法相比较的话,super()会自动将self传给父类的__init__,因此不需要加self
            super().__init__(name, age, sex)
            self.sal = sal
    
        def change_score(self):
            print('老师%s修改分数...' % self.name)
    
    
    class Student(People):
        def __init__(self, name, age, sex, girl):
            super().__init__(name, age, sex)
            self.girl = girl
    
        def choose_course(self):
            print('学生%s选择课程...' % self.name)
    
    
    stu1 = Student('韩', 20, 'male', '李雷')
    t1 = Teacher('tank', 30, 'male', 20000)
    print(stu1.name, stu1.age, stu1.sex, stu1.school, stu1.girl)
    print(t1.name, t1.age, t1.sex, t1.sal)

    六、了解

    1、经典类与新式类

    1.1 新式类

    • 凡是继承了object的类或者子类都是新式类
    • 在python3中所有的类都默认继承object

    1.2 经典类

    • 在python2中才会有经典类与新式类之分
    • 在python2中,凡是没有继承object的类,都是经典类
    class User(object):  # 继承object
        pass
    
    class User:  # (<class 'object'>,)
        x = 10
        pass
    
    
    class Sub(User):
        pass
    
    
    print(User.__dict__)
    
    print(object)

    2、super严格遵循mro继承顺序

    调用super返回的是一个继承序列:

    super的继承顺序严格遵循mro继承顺序
    
    class Father1:
        # x = 10
        pass
    
    
    class Father2:
        # x = 20
        pass
    
    
    # 多继承的情况下: 从左到右
    class Sub(Father1, Father2):
    
        # 注意: __int__ 不是 __init__
        def __init__(self):
            print(super().__delattr__)
    
    
    print(Sub.mro())  # 可以打印查看mro继承的序列
    obj = Sub()
    print(object)

    在python3中提供了一个查找新式类的查找顺序的内置方法:

    • mro():会把当前类的继承关系列出来
    注意: super()会严格按照mro列表的顺序往后查找
    class A:
        def test(self):
            print('from A.test')
            super().test()
    
    
    class B:
        def test(self):
            print('from B.test')
    
    
    class C(A, B):
        pass
    
    
    c = C()
    # 检查super的继承顺序
    print(C.mro())
    
    # 去A找,有的话打印,然后super又执行了test,根据mro中查找打印B类中test。
    c.test()
    
    
    '''
    from A.test
    from B.test
    '''

    3、钻石继承(菱形继承)

    多继承情况下回造成砖石继承

    mro的查找顺序:

    • 新式类:
      • 广度优先
    • 经典类
      • 深度优先

    新式类:

    经典类:





  • 相关阅读:
    红外应用
    电池分类及特点
    温湿度传感器AM2302(DH22)
    lora
    跳频扩频技术学习
    STM32F4/F7运算性能
    物联网相关模块
    DC-DC芯片
    外部引用CSS中 link与@import的区别
    超酷实用的jQuery焦点图赏析及源码
  • 原文地址:https://www.cnblogs.com/cnhyk/p/11937435.html
Copyright © 2020-2023  润新知