一 继承的概念
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 中,一个子类时可以同时继承多个父类的,这固然可以带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发菱形问题(或称钻石问题),菱形结构如下:
#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列表中顺序来查找
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 的类及子类,都是经典类
属性查找顺序如图所示:
当类是经典类时,多继承情况下,在要查找属性不存在时 ,会按照深度优先的凡是查找下去
示例一:经典类
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
新式类:广度优先,会在检索最后一条分支的时候检索大脑袋
属性查找顺序如下图:
当类是新式类,多继承情况下,在要查找属性不存在时,会按照广度优先的方式下查找下去
示例二:新式类
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类越多,子类的代码可读性就会越差。