【4.0】Python面向对象之派生

发布时间 2023-11-30 14:25:19作者: Chimengmeng

【一】什么是派生

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

【二】派生的方法

  • 子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找

  • 例如每个老师还有职称这一属性

  • 我们就需要在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)


# 只会找自己类中的__init__,并不会自动调用父类的
obj = Teacher('dream', 'male', 18, '高级讲师')

print(obj.name, obj.sex, obj.age, obj.title)
# dream male 18 高级讲师
  • 很明显子类Teacher中__init__内的前三行又是在写重复代码
  • 若想在子类派生出的方法内重用父类的功能,有两种实现方式

【1】指名道姓的调用某一个类的函数

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):
        # 直接调用 父类 中 的 __init__ 方法
        # 调用的是函数,因而需要传入self
        People.__init__(self, name, age, sex)
        self.title = title

    def teach(self):
        print('%s is teaching' % self.name)


# 只会找自己类中的__init__,并不会自动调用父类的
obj = Teacher('dream', 'male', 18, '高级讲师')

print(obj.name, obj.sex, obj.age, obj.title)
# dream male 18 高级讲师

【2】超类(super())

  • 调用super()会得到一个特殊的对象
  • 该对象专门用来引用父类的属性
  • 且严格按照MRO规定的顺序向后查找
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):
        # 直接调用 父类 中 的 __init__ 方法
        # 调用的是绑定方法,因此会自动传入self,但是需要传入相应的参数
        super().__init__(name, sex, age)
        self.title = title

    def teach(self):
        print('%s is teaching' % self.name)


# 只会找自己类中的__init__,并不会自动调用父类的
obj = Teacher('dream', 'male', 18, '高级讲师')

print(obj.name, obj.sex, obj.age, obj.title)
# dream male 18 高级讲师
  • 提示:在Python2中super的使用需要完整地写成super(自己的类名,self) ,而在python3中可以简写为super()。

  • 当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。

  • 只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次

  • 注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表

【3】小结

  • 这两种方式的区别是:
    • 方式一是跟继承没有关系的,而方式二的super()是依赖于继承的
    • 并且即使没有直接继承关系,super()仍然会按照MRO继续往后查找
# A没有继承B
class A:
    def test(self):
        super().test()


class B:
    def test(self):
        print('from B')


class C(A, B):
    pass


# 在代码层面A并不是B的子类
# 但从MRO列表来看,属性查找时,就是按照顺序C->A->B->object,B就相当于A的“父类”
print(C.mro())
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

obj = C()

# 属性查找的发起者是类C的对象obj
# 所以中途发生的属性查找都是参照C.mro()
obj.test()
# from B
  • obj.test()首先找到A下的test方法
    • 执行super().test()会基于MRO列表(以C.mro()为准)当前所处的位置继续往后查找()
    • 然后在B中找到了test方法并执行。
  • 关于在子类中重用父类功能的这两种方式
    • 使用任何一种都可以
    • 但是在最新的代码中还是推荐使用super()
#指名道姓
class A:
    def __init__(self):
        print('A的构造方法')
class B(A):
    def __init__(self):
        print('B的构造方法')
        A.__init__(self)


class C(A):
    def __init__(self):
        print('C的构造方法')
        A.__init__(self)


class D(B,C):
    def __init__(self):
        print('D的构造方法')
        B.__init__(self)
        C.__init__(self)

    pass
f1=D() #A.__init__被重复调用
'''
D的构造方法
B的构造方法
A的构造方法
C的构造方法
A的构造方法
'''
#使用super()
class A:
    def __init__(self):
        print('A的构造方法')
class B(A):
    def __init__(self):
        print('B的构造方法')
        super(B,self).__init__()


class C(A):
    def __init__(self):
        print('C的构造方法')
        super(C,self).__init__()


class D(B,C):
    def __init__(self):
        print('D的构造方法')
        super(D,self).__init__()

f1=D() #super()会基于mro列表,往后找
'''
D的构造方法
B的构造方法
C的构造方法
A的构造方法
'''

【4】小练习

# A没有继承B,但是A内super会基于C.mro()继续往后找
class A:
    def test(self):
        print('A---->test')
        super().aaa()


class B:
    def test(self):
        print('B---->test')

    def aaa(self):
        print('B---->aaa')


class C(A, B):
    def aaa(self):
        print('C----->aaa')


c = C()
c.test()  
# 打印结果:
'''
A---->test
B---->aaa
'''

# C作为方法调用(即c.test())的发起者,方法调用过程中涉及的属性查找都参考C.mro()。
# 父子关系按照mro列表为准,千万不要从代码层面看父子。例如
print(C.mro())
# c.test(),发起者C.mro()列表如下
# 从列表中可以看出,B类就是A类他爹,而代码层面二者并无继承关系
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]