面向对象的三大特性之继承

一 继承的概念

1、什么是继承

继承是一种创建新类的的方式,新建的类可以称为子类或派生类,被继承的类称为父类,父类又可称为基类或超类,子类会遗传父类的属性

类与类之间的继承指的是什么’是’什么的关系(比如人类,猪类,猴类都是动物类)

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

class Parent1(object): 
    x=1111

class Sub1(Parent1): # 单继承
    pass

print(Sub1.x)  #111

1.1 经典类与新式类

在 python2 中,有经典类与新式类之分:

新式类:继承了object 类的子类, 以及该子类的子子类被称为新式类

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

在 python3 中,没有继承任何类的话,默认继承 object 类,所以 python3 中所有类都是新式类

1.2 查看子类继承的所有父类

通过类的内置属性__ bases__可以查看类继承的所有父类

>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

2、为何要用继承

优点:用来解决类与类之间代码冗余问题

缺点:将类耦合到了一起

2.1 python 的多继承

python 支持多继承,即在 python 中,新建的类可以继承一个或者多个父类。

class Parent1(object):
    x=1111

class Parent2(object):
    pass

class Sub1(Parent1): # 单继承
    pass

class Sub2(Parent1,Parent2): # 多继承
    pass

#查看类的基类(父类)
print(Sub1.__bases__) #(<class '__main__.Parent1'>,)
print(Sub2.__bases__) #(<class '__main__.Parent1'>, <class '__main__.Parent2'>)

print(Sub1.x)  #111

优点:子类可以同时遗传多个父类的属性,最大程度地重用父类的属性

缺点:

​ 1、违背了人的思维习惯:继承表达的是一种什么'是'什么的关系

​ 2、代码可读性会变差

​ 3、不建议使用多继承,有可能会引可恶的菱形问题,扩展性变差

​ 如果真的涉及到一个子类不可避免地要重用多个父类属性,应该使用 Mixins

3 如何实现继承

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

class OldboyPeople:
    school = 'OLDBOY'

    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex


class Student(OldboyPeople):
    def choose_course(self):
        print('学生%s 正在选课' % self.name)
# stu_obj = Student('lili', 18, 'female')
# print(stu_obj.__dict__)
# print(stu_obj.school)
# stu_obj.choose_course()


class Teacher(OldboyPeople):
    #           老师的空对象,'egon',18,'male',3000,10
    def __init__(self, name, age, sex, salary, level):
        # 指名道姓地跟父类OldboyPeople去要__init__
        OldboyPeople.__init__(self,name,age, sex) ##调用的是函数,因而需要传入self
        self.salary = salary
        self.level = level

    def score(self):
        print('老师 %s 正在给学生打分' % self.name)

tea_obj=Teacher('egon',18,'male',3000,10)
print(tea_obj.__dict__) #{'name': 'egon', 'age': 18, 'sex': 'male', 'salary': 3000, 'level': 10}
print(tea_obj.school) #OLDBOY
tea_obj.score() #老师 egon 正在给学生打分

3.1 派生与继承

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

解决问题:子类重用父类的属性,并派生出新的属性

  两种方式:

    1.直接引用父类的__ init__为其传参,并添加子类的属性,它是指名道姓调用某一个类下的函数,不依赖与继承关系

    2.通过super来指向父类的属性,super()调用父类提供自己的方法,严格依赖继承关系

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

    注意:使用哪一种都可以,但不能两种方式混合使用

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):
        People.__init__(self, name, age, sex) #'指名道姓'地调用某一个类的函数
         self.title = title
    def teach(self):
        print('%s is teaching' %self.name)
# obj = Teacher('lili','female', 28, '高级教师') #只会找自己类中的__init__
#
# print(obj.teach())  #lili is teaching
                    	

#方法二:super()
'''
调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找
'''
class Teacher(People):
    def __init__(self, name, sex, age, title):
        super().__init__(name, age, sex) #调用的对象的是绑定方法,自动传入self   #按照Teacher.MRO列表的从 当前类(调用 super 的类)后面的父类 往后查找
        self.title = title
    def teach(self):
        print('%s is teaching' %self.name)

obj = Teacher('lili','female', 28, '高级教师') #只会找自己类中的__init__

print(obj.teach())  #lili is teaching
                    

注意:在python2中super 的使用需要完整地写成super(自己的类名,self),而在python3中可以简写为super()。

这两种方法的区别是:方式一是跟继承没有关系的,它是通过类调用函数方法,而方式二的 super()是依赖继承的,并且没有直接继承关系,super()仍然会按照 MRO 继续往后查找

关于在子类中重用父类功能的这两种方式,使用任何一种都可以,但是在最新的代码中还是推荐使用super()

案例一:

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

class A:
       print('from A')
        super().test()  #按照D.MRO列表,从当前类A往后查找(不包含类A)

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

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

obj=C() 
print(C.mro())#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
obj.test()

#结果展示
'''
from A
from B  
'''

案例二:

class A:
    def test(self):
        print('from A')
        super().test1()

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

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

obj=C() #from C
print(C.mro())#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
obj.test()
#结果展示
'''
from A
报错:'super' object has no attribute 'test1'
'''
print(C.mro())#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
obj.test()
#结果展示
'''
from A
报错:'super' object has no attribute 'test1'
'''
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

'''
报错的原因的是调用super(),它是按照调用者的.MRO列表中当前类(A)往后查A(不包含 A类),即从 B类往后找所以找不到
'''

案例三:

class D:
    def test1(self):
        print('from D')

class A:
    def test(self):
        print('from A')
        super().test1()

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

class C(A,B,D):
   def test1(self):
       print('from C')

obj=C()
print(C.mro())
obj.test()
#结果展示
'''
[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class 'object'>]
from A
from D
'''

3.2 属性查找问题

单继承背景下的属性查找

示范一:

class Foo:
    def f1(self):
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.f1() # obj.f1()

class Bar(Foo):
    def f1(self):
        print('Bar.f1')

obj=Bar()
obj.f2()
#结果展示
'''
Foo.f2
Bar.f1
'''

示范二:想要调用的当前类的属性

class Foo:
    def f1(self):
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        Foo.f1(self) # 调用当前类中的f1

class Bar(Foo):
    def f1(self):
        print('Bar.f1')

obj=Bar()
obj.f2()
#结果展示
'''
Foo.f2
Foo.f1
'''

父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方法将方法设置为私有的

示范三:

class Foo:
    def __f1(self): # _Foo__f1
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.__f1() # self._Foo__f1,# 调用当前类中的f1

class Bar(Foo):
    def __f1(self): # _Bar__f1
        print('Bar.f1')

obj=Bar()
obj.f2()

#结果展示
'''
Foo.f2
Foo.f1
'''

4、多继承的菱形问题与属性查找

4.1 菱形问题介绍

大多数面向对象 1 都不支持多继承,在 python 中,一个子类时可以同时继承多个父类的,这固然可以带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发菱形问题(或称钻石问题),菱形结构如下:

v2-7c6088a106bbf1fc46de0cca9c03a037_1440w

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

#注菱形继承(最终继承的汇集点不是 object)
#非菱形继承(最终继承的汇集点是 object)

示例一:这不是菱形继承

class A(object):
    pass


class B(object):
    pass


class C(A, B):
    pass
  
 #这不菱形继承,最终汇集点为 object

示例二:菱形继承

class D(object):
    pass

class A(D):
    pass


class B(D):
    pass


class C(A, B):
    pass
  
#这是菱形继承,最终汇集点为 类D

4.2 继承原理

对于定义的每一个类,python 都会计算出一个方法解析顺序(MRO)列表,该 MRO 列表就是一个简单的所有基类的线性顺序列表,如下:

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

print(D.mro()) # [<class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

由类发起的属性查找准则:

1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查

由对象发起的属性查找准则:

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

4.3 深度优先和广度优先

1 非菱形继承下的属性查找

如果多继承是非菱形继承,经典类与新式的属性查找顺序一样:

都是按照 MRO列表中顺序来查找

image-20200409185422400

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


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


class B(E):
    def test(self):
        #print('from B')
        pass


class C(F):
    def test(self):
        print('from C')


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


class A(B, C, D):
    # def test(self):
    #     print('from A')
    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()
obj.test() # 结果为:from E
# 可依次注释上述类中的方法test来进行验证

2 菱形继承下的属性查找

经典类:深度优先,会在检索第一条分支的时候就直接一条道走到黑,即会检索大脑袋(共同的父类),此时无 object 类

​ 注:在 python2 中,未继承 object 的类及子类,都是经典类

属性查找顺序如图所示:

image-20200409185805284

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

示例一:经典类

class G: # 在python2中,未继承object的类及其子类,都是经典类
    pass

class E(G):
    pass

class F(G):
    def test(self):
        print('from F')

class B(E):
    pass

class C(F):
    def test(self):
        print('from C')

class D(G):
    def test(self):
        print('from D')

class A(B,C,D):
    pass
 

# 经典类:A->B->E->G->C->F->D
obj = A()
obj.test() # from C

​ 新式类:广度优先,会在检索最后一条分支的时候检索大脑袋

属性查找顺序如下图:

image-20200409190005443

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

示例二:新式类

class G(object):
    def test(self):
        print('from G')

class E(G):
    def test(self):
        print('from E')

class F(G):
    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(G):
    def test(self):
        print('from D')

class A(B,C,D):
    # def test(self):
    #     print('from A')
    pass

obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->C->F->D->G->object
# 可依次注释上述类中的方法test来进行验证

总结

多继承要用时,需要规避以下几个问题

1、继承结构尽量不要过于繁杂

2、推荐使用 mixins 机制:在多继承的背景下满足继承的什么'是'什么关系

二 mixins 机制

mixins机制核心:就是在多继承背景下尽可能地提升多继承的可读性

ps:让多继承满足人的思维习惯==》什么'是'什么关系

举例说明:

​ python 提供了 MIxins 机制,简单来说, MIxins机制指的是子类混合(mixin)不同类的功能,这些类 1 采用统一的命名规范(例如 MIxin后缀),以此表示这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的(该类不是作为父类,只是提供了一种功能方法)

class Vehicle:  # 交通工具  #父类
    pass


class FlyableMixin:  #定义功能类
    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")


class CivilAircraft(FlyableMixin, Vehicle):  # 民航飞机
    pass


class Helicopter(FlyableMixin, Vehicle):  # 直升飞机
    pass


class Car(Vehicle):  # 汽车
    pass

# ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路

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

使用 MIxin 类实现多继承注意事项:

1、首先它必须表示一个功能而非某个物品,python 对于 mixin类的命名方式一般以 Mixin,able,ible 为后缀

2、它的功能或者职责必须单一,如果需要多个功能,那就多定义些 Mixin 类,一个类可以继承多个 Mixin,为了保证遵循继承的'is-a'原则,只能继承一个标识其归属含义的父类

3、它不依赖于子类的实现

4、子类即便没有继承这个 Mixin 类,也可以正常工作,只是缺少了该部分功能

Ps:Mixins是从多个类中重用代码的好方法,但是需要付出相应的代价,我们定义的Minx类越多,子类的代码可读性就会越差。

原文地址:https://www.cnblogs.com/xy-han/p/12669757.html