目录
一、继承介绍
1.1、什么是继承
继承是一种创建新类的方式,在Python中,新建的类可以继承一个或多个父类,新建的类可称为子类或派生类,父类又可称为基类或超类
class ParentClass1: #定义父类
pass
class ParentClass2: #定义父类
pass
class SubClass1(ParentClass1): #单继承
pass
class SubClass2(ParentClass1,ParentClass2): #多继承
pass
通过类的内置属性__bases__可以查看类继承的所有父类
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
1.2、为何要继承
子类会遗传父类的属性,所以继承是用来解决类与类之间代码冗余问题
1.3、如何实现继承
class Parent1:
pass
class Parent2:
pass
class Sub1(Parent1):
pass
class Sub2(Parent1,Parent2):
pass
print(Sub1.__bases__)
print(Sub2.__bases__)
继承案例
class OldboyPeople:
school = "oldboy"
class Student(OldboyPeople):
def __init__(self,name,age,gender,stud_id,course):
self.name = name
self.age = age
self.gender = gender
self.stu_id = stud_id
self.course = course
def choose(self):
print('%s 正在选课' %self.name)
class Teacher(OldboyPeople):
def __init__(self,name,age,gender,salary,level):
self.name = name
self.age = age
self.gender = gender
self.salary = salary
self.level = level
def score(self,stu,num):
stu.num = num
stu1=Student("艾利克斯",73,'male',1001,"python全栈开放")
tea1=Teacher("egon",18,'male',2000,10)
print(stu1.school)
二、在子类派生的新方法中重用父类的功能
方式一:指名道姓地调用某一个类的函数
特点:不依赖于继承关系
class OldboyPeople:
school = "oldboy"
# 空对象,"艾利克斯",73,'male'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def f1(self):
print('1111111')
class Student(OldboyPeople):
# 空对象,"艾利克斯",73,'male',1001,"python全栈开放"
def __init__(self,name,age,gender,stu_id,course):
OldboyPeople.__init__(self,name,age,gender) # OldboyPeople.__init__(空对象,"艾利克斯",73,'male')
self.stu_id = stu_id
self.course = course
def choose(self):
print('%s 正在选课' %self.name)
def f1(self):
OldboyPeople.f1(self)
print("22222")
class Teacher(OldboyPeople):
def score(self,stu,num):
stu.num = num
stu1=Student("艾利克斯",73,'male',1001,"python全栈开放")
# tea1=Teacher("egon",18,'male',2000,10)
stu1.f1() # 1111111 22222
三、属性查找
有了继承关系,对象在查找属性时,先从对象自己的__dict__中找,如果没有则去子类中找,然后再去父类中找……
例一:
class Foo:
def f2(self):
print("Foo.f2")
def f1(self):
print('Foo.f1')
self.f2() # obj.f2()
class Bar(Foo):
def f2(self):
print("Bar.f2")
obj = Bar()
obj.f1()
运行结果:
Foo.f1
Bar.f2
例二:父类如果不想让子类覆盖自己的方法,可以在方法名前加前缀_
class Foo:
def __f2(self): # _Foo__f2
print("Foo.f2")
def f1(self):
print('Foo.f1')
self.__f2() # obj._Foo__f2()
class Bar(Foo):
def __f2(self): # _Bar__f2
print("Bar.f2")
obj = Bar()
obj.f1()
运行结果:
Foo.f1
Foo.f2
四、继承的实现原理
4.1、新式类与经典类
新式类:但凡是继承了object类的子类,以该子类子子孙孙类都称之为新式类
经典类:没有继承了object类的子类,以该子类子子孙孙类都称之为经典类
python3中全都是新式类,python2中才有经典类:
在python3中没有继承任何类的类会默认继承object类
class Foo(object):
pass
print(Foo.__bases__)
4.2、菱形问题
大多数面向对象语言都不支持多继承,而在Python中,一个子类是可以同时继承多个父类的,这固然可以带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发著名的 Diamond problem菱形问题(或称钻石问题,有时候也被称为“死亡钻石”),菱形其实就是对下面这种继承结构的形象比喻
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RGkEvzy3-1596698691515)(C:\Users\高雨\AppData\Roaming\Typora\typora-user-images\image-20200806145641559.png)]
这种继承结构下导致的问题称之为菱形问题:如果A中有一个方法,B和/或C都重写了该方法,而D没有重写它,那么D继承的是哪个版本的方法:B的还是C的?如下所示
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
要想搞明白obj.test()是如何找到方法test的,需要了解python的继承实现原理
4.3、继承原理
python到底是如何实现继承的呢? 对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表,如下
>>> D.mro() # 新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置该方法
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。 而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
所以obj.test()的查找顺序是,先从对象obj本身的属性里找方法test,没有找到,则参照属性查找的发起者(即obj)所处类D的MRO列表来依次检索,首先在类D中未找到,然后再B中找到方法test
1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去,
2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去,
4.4、深度优先和广度优先
非菱形结构经典类和新式类都采用广度优先的方式查找
参照下述代码,多继承结构为非菱形结构,此时,会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性
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')
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 B
# 可依次注释上述类中的方法test来进行验证
继承关系为菱形结构,那么经典类与新式类会有不同MRO,分别对应属性的两种查找方式:深度优先和广度优先
class G: # 在python2中,未继承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->G->C->F->D->object
# 可依次注释上述类中的方法test来进行验证,注意请在python2.x中进行测试
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来进行验证
4.5、Pyton Mixins机制
一个子类可以同时继承多个父类,这样的设计常被人诟病,一来它有可能导致可恶的菱形问题,二来在人的世界观里继承应该是个”is-a”关系。 比如轿车类之所以可以继承交通工具类,是因为基于人的世界观,我们可以说:轿车是一个(“is-a”)交通工具,而在人的世界观里,一个物品不可能是多种不同的东西,因此多重继承在人的世界观里是说不通的,它仅仅只是代码层面的逻辑。不过有没有这种情况,一个类的确是需要继承多个类呢?
答案是有,我们还是拿交通工具来举例子:
民航飞机、直升飞机、轿车都是一个(is-a)交通工具,前两者都有一个功能是飞行fly,但是轿车没有,所以如下所示我们把飞行功能放到交通工具这个父类中是不合理的
class Vehicle: # 交通工具
def fly(self):
'''
飞行功能相应的代码
'''
print("I am flying")
class CivilAircraft(Vehicle): # 民航飞机
pass
class Helicopter(Vehicle): # 直升飞机
pass
class Car(Vehicle): # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
pass
Python提供了Mixins机制,简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”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,而不是Flyable,这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类,表示混入(mix-in),这种命名方式就是用来明确地告诉别人(python语言惯用的手法),这个类是作为功能添加到子类中,而不是作为父类。
参考文件:https://zhuanlan.zhihu.com/p/109331525