面向对象之继承
class继承
一:继承介绍
什么是继承?
继承就是创建了一个新的类,在Python中,新创建的类可以继承一个或者多个父类(现实社会有点难)新建的类就是子类,围着派生类,父类称之为基类或者超类(累)
为什么要有继承?
继承可以减少代码冗余
如何使用继承?
class P1:
pass
class P2:
pass
class sub1(P1): # 继承了P1类
pass
class sub2(P1,P2): # 继承了P1,P2类
pass
通过类内的__bases__可以查看类继承的所有的父类
print(sub1.__bases__) # (<class '__main__.P1'>,)
print(sub2.__bases__) # (<class '__main__.P1'>, <class '__main__.P2'>)
示列一(继承)
class School:
scholl = '东京校区'
class Student(School):
def __init__(self,name,age,gender,suid,course):
self.name = name
self.age = age
self.gender = gender
self.suid = suid
self.course = course
def choose(self):
print(f"{self.name}正在选课")
class Teacher(School):
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,student,num):
student.num = num
s1 = Student('alen',66,'male',10001,'python开发')
t1 = Teacher('eg',18,'male',2000,10)
print(t1.scholl) # 东京校区
print(s1.__dict__) # {'name': 'alen', 'age': 66, 'gender': 'male', 'suid': 10001, 'course': 'python开发'}
print(t1.__dict__) # {'name': 'eg', 'age': 18, 'gender': 'male', 'salary': 2000, 'level': 10}
二:如何在子类中派生新方法重用父类的功能
方式一:指名道姓地调用某一个类的函数
特点:可以不以依赖继承关系
示列
class School:
scholl = '东京校区'
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def t1(self):
print("这是一个测试继承的函数")
class Student(School):
def __init__(self,name,age,gender,suid,course):
School.__init__(self,name,age,gender)
self.suid = suid
self.course = course
def choose(self):
print(f"{self.name}正在选课")
class Teacher(School):
def __init__(self,name,age,gender,salary,level):
School.__init__(self,name,age,gender)
self.salary = salary
self.level = level
def score(self,student,num):
School.t1(self)
student.num = num
s1 = Student('alen',66,'male',10001,'python开发')
t1 = Teacher('eg',18,'male',2000,10)
print(s1.__dict__) # {'name': 'alen', 'age': 66, 'gender': 'male', 'suid': 10001, 'course': 'python开发'}
print(t1.__dict__) # {'name': 'eg', 'age': 18, 'gender': 'male', 'salary': 2000, 'level': 10}
三:属性查找
在python中如果对象内部没有属性,就去类里去找,如果类里没有就去父类找(如果没有有继承,就找不到报错)
在python3中会去object里去找,(如果没有默认继承,默认就是继承object类),在python2中,存在两种类(新式类和经典类),两种类的查找循序有点区别
示列
class Foo:
def f2(self):
print("from foo.f2")
def f1(self):
print("from foo.f1")
self.f2()
class Bar(Foo):
def f2(self):
print("from Bar.f2")
t = Bar()
t.f1()
"""
from foo.f1
from Bar.f2
"""
父类如果不想让子类覆盖自己的方法,可以在方法名前加__,让其成为私有属性
class Foo:
def __f2(self):
print("from foo.f2")
def f1(self):
print("from foo.f1")
self.__f2()
class Bar(Foo):
def __f2(self):
print("from Bar.f2")
obj = Bar()
obj.f1()
```
from foo.f1
from foo.f2
```
四:继承的原理
基础知识
新式类:凡是继承了object的子类,以该子类子子孙孙类都称之为新式类
经典类:没有继承object的子类,以该子类子子孙孙类都称之为经典类
在python3中全都是新式类,python2中有经典类
在python3中没有继承任何类,默认基础obj类
在py3中
class Foo:
pass
print(Foo.__bases__) # (<class 'object'>,)
在py2中
class Foo:
pass
print(Foo.__bases__) # 为空
继承的实现原理
1:菱形问题
大多数面向对象语言都不支持多继承,在python中,一个子类是可以同时继承多个父类的,这个可以让一个子类,可以对多个不同父类加以重用的好处,但是也有可能引发著名的菱形问题(或称死亡钻石问题)
如图
![菱形继承](D:\Program Files (x86)\markdown\python3.8\菱形继承.png)
A类在顶部,B类和C类分别位于其下方,D类在底部将两者连接在一起形成菱形。
这个图展示的就是菱形继承
如果A中有一个方法,B或者C都重写了该方法,而D没有重写,那么D继承的是哪个版本的方法:是B或者是C?看下面
在py3中
class A:
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的继承实现原理--
2继承原理
MRO
Python是如何实现继承的呢?
对于你定义的每一个类,python都会计算出方法解析顺(mro)列表,该mro列表就是一个简单的所有基类的线性顺序列表,如下
print(D.mro())
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
python会在mro列表上从左到右开始查找基类,直到找到一个第一匹配这个属性的类为止
这个mro列表的构造是通过一个C3线性算法来实现的。
这个算法实际上就是合并所有父类的mro列表并遵循如下原则
1:子类会先于父类查找
2:多个父类会根据它们所在列表中的顺序被检查
3:如果对下一个类存在两个合法的选择,选择第一个父类
属性查找
1:由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类的mro列表规定的顺序依次找下去
2:由类发起的属性查找,会按照当前类.mro规定的顺序依次找下去
示列
#【test1】
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(C,B):
def test(self):
print('from D')
print(D.mro()) # 类D以及类D的对象访问属性都是参照该类的mro列表
# 输出结果:
# [<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
obj1 = D()
obj1.test() # 输出结果:from D
print(D.test) # 输出结果:<function D.test at 0x0000012EA27FA4C0>
print(C.mro()) # 类C以及类C的对象访问属性都是参照该类的mro列表
# 输出结果:[<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
obj2 = C()
obj2.test() # 输出结果:from C
print(C.test) # 输出结果: <function C.test at 0x0000022FA7D0A3A0>
# 【test2】
class A(object):
# def test(self):
# print('from A')
pass
class B(A):
def test(self):
print('from B')
class C(A):
# def test(self):
# print('from C')
pass
class D(C,B):
# def test(self):
# print('from D')
pass
print(D.mro()) # 类D以及类D的对象访问属性都是参照该类的mro列表
# 输出结果:
# [<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
obj1 = D()
obj1.test() # 输出结果:from B
print(D.test) # 输出结果:<function B.test at 0x00000246D6F3A310>
# 总结:类相关的属性查找(类名.属性,该类的对象.属性),都是参照该类的mro
3:深度优先和广度优先
1:非菱形继承
如果多继承是非菱形继承,经典类与新式类的属性查找顺序一样:都是一个分支一个分支地找下去,然后找object类
示列如下
![image-20200807201837419](D:\Program Files (x86)\markdown\python3.8\image-20200807201837419.png)
代码演示
class E:
# def test(self):
# print('from E')
pass
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')
pass
class D:
def test(self):
print('from D')
class A(B, C, D):
# def test(self):
# print('from A')
pass
# 新式类
print(A.mro())
# A->B->E->C->F->D->object
# [<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 F
2:菱形继承
如果多继承是菱形继承,经典类与新式类的属性查找顺序不一样
经典类:深度优先,会在在检索第一天分支的时候就直接一天道走到黑,即会检索(共同的父类)
新式类:广度优先,会在检索最后一条分支的时候检索共同的父类
![image-20200807202406380](D:\Program Files (x86)\markdown\python3.8\image-20200807202406380.png)
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
# 如果B没有去找C,c没有然后找A
python到底是如何实现继承的呢? 对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO)列表,该MRO列表就是一个简单的所有基类的线性顺序列表,如下
print(d.mro) # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
class G: # 在python2中,未继承object的类及其子类,都是经典类
# def test(self):
# print('from G')
pass
class E(G):
# def test(self):
# print('from E')
pass
class F(G):
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(G):
def test(self):
print('from D')
class A(B,C,D):
# def test(self):
# print('from A')
pass
# # 新式类
print(A.mro())
# # A->B->E->C->F->D->G->object
# # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>,
# # <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
obj = A()
obj.test() # 输出结果:from C
# 经典类:A->B->E->G->C->F->D
print(A.mro())
obj = A()
obj.test() # 输出结果:from C
3:总结
多继承到底要不要用?
python既然提供了,那么就要用,但是要规避问题
1:继承结构尽量不要过于复杂
2:推荐使用mixins机制:在多继承的背景下满足继承是什么关系
四:python Mixins机制
用来解决多继承所带来的问题的,将原本的混乱的继承清晰的变成’is-a‘的关系
# 民航飞机、直升飞机、汽车都是交通工具,但是飞机会飞,汽车不会,
# 这个时候我们将飞这个功能放到交通工具中就显得不合适
class Vehicle: # 交通工具
def fly(self):
'''
飞行功能相应的代码
'''
print("I am flying")
class CivilAircraft(Vehicle): # 民航飞机
pass
class Helicopter(Vehicle): # 直升飞机
pass
class Car(Vehicle): # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
pass
# 针对上面的问题,就引入Mixin机制
# Mixin机制指的就是子类混合不同类的功能,然后将其用统一的命名规范来标识该类只是用来混合功能的
# 从而来遵守多继承下的“is-a”关系,Mixin机制本质仍是多继承
class Vehicle: # 交通工具
pass
class FlyMixin:
def fly(self):
'''
飞行功能相应的代码
'''
print("I am flying")
class CivilAircraft(FlyMixin,Vehicle): # 民航飞机
pass
class Helicopter(FlyMixin,Vehicle): # 直升飞机
pass
class Car(Vehicle): # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
pass
'''
使用Mixin类实现多重继承要非常小心
1、它必须表示某一种功能,而非物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
2、它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承
的“is-a”原则,只能继承一个标识其归属含义的父类
3、然后,它不依赖于子类的实现
4、最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。
(比如飞机照样可以载客,就是不能飞了)
'''
五:派生与方法重用
-
子类中衍生出的新东西
- 子类独有,父类没有
- 子类有,父类也有,子类是完全覆盖父类
- 子类有,父类也有,子类在父类的基础上进行扩展
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)
```
-
想在子类派生出的方法内重用父类的功能,有两种实现方式
# 方法一:“指名道姓”地调用某一个类的函数=》不依赖于继承 >>> class Teacher(People): ... def __init__(self,name,sex,age,title): ... People.__init__(self,name,age,sex) #调用的是函数,因而需要传入self ... self.title=title ... def teach(self): ... print('%s is teaching' %self.name) # 方法二:super()调用父类提供给自己的方法=》严格依赖继承关系 # 调用super()会得到一个特殊的对象,该对象会按照发起属性查找的类的mro去当前类的父类中找属性 >>> class Teacher(People): ... def __init__(self,name,sex,age,title): ... super().__init__(name,age,sex) #调用的是绑定方法,自动传入self ... self.title=title ... def teach(self): ... print('%s is teaching' %self.name) >>> #A没有继承B ... class A: ... def test(self): ... super().test() ... >>> class B: ... def test(self): ... print('from B') ... >>> class C(A,B): ... pass ... >>> C.mro() # 在代码层面A并不是B的子类,但从MRO列表来看,属性查找时,就是按照顺序C->A->B->object,B就相当于A的“父类” [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>,<class ‘object'>] >>> obj=C() >>> obj.test() # 属性查找的发起者是类C的对象obj,所以中途发生的属性查找都是参照C.mro() from B
六:组合
什么是组合?
- 一个类中以另外一个类的对象作为数据属性,作为类的组合
# 组合与继承都是用来解决代码的重用性问题。 # 二者的区别在于 ''' 继承:强调‘是’的关系,例如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承 组合:强调‘有’的关系,例如老师有多门课程,老师有给学生打分,当类之间有显著不同,并且较小的类是较大的类所需 要的组件时,应该使用组合 ''' class Course: def __init__(self,name,period,price): self.name=name self.period=period self.price=price def tell_info(self): print('<%s %s %s>' %(self.name,self.period,self.price)) class Date: def __init__(self,year,mon,day): self.year=year self.mon=mon self.day=day def tell_birth(self): print('<%s-%s-%s>' %(self.year,self.mon,self.day)) class People: school='清华大学' def __init__(self,name,sex,age): self.name=name self.sex=sex self.age=age #Teacher类基于继承来重用People的代码,基于组合来重用Date类和Course类的代码 class Teacher(People): #老师是人 def __init__(self,name,sex,age,title,year,mon,day): super().__init__(name,age,sex) self.birth=Date(year,mon,day) #老师有生日 self.courses=[] #老师有课程,可以在实例化后,往该列表中添加Course类的对象 def teach(self): print('%s is teaching' %self.name) python=Course('python','3mons',3000.0) linux=Course('linux','5mons',5000.0) teacher1=Teacher('lili','female',28,'博士生导师',1990,3,23) # teacher1有两门课程 teacher1.courses.append(python) teacher1.courses.append(linux) # 重用Date类的功能 teacher1.birth.tell_birth() # 重用Course类的功能 for obj in teacher1.courses: obj.tell_info()
参考