Loading

面向对象之继承

继承的介绍

继承是一种创建新类的方式,新建的类可以称为子类或派生类, 父类又可称为基类或超类, 子类会遗传父类的属性和方法
需要注意的是: 在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方法并执行。

 

posted @ 2020-07-18 15:25  愚者丶  阅读(159)  评论(0编辑  收藏  举报