(摘录自 egon老师博客)
继承是一种创建新类的方式,新建的类可以继承一个后多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类
子类会 遗传 父类的属性,从而解决代码重用的问题
python中类的继承分为 单继承和多继承
class ParentClass1: 定义父类
pass
class ParentClass2: 定义父类
pass
class SubClass1(ParentClass1): 单继承ParentClass1,派生类是SubClass
pass
calss SubClass2(ParentClass1,ParentClass2): python支持多继承,用逗号隔开多个继承的类
pass
查看继承
>>> SubClass1.__bases__#__base__只看从左到右继承的第一个类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>>SubClass2.__bases__
(<class '__main__.ParentClass1'>,<class'__main__.ParentClass2'>)
在python3中无论是否继承object,都默认继承object,即python3中所有类均是新式类
python需要显式声明继承object类 才是新式类 否则都是经典类
如果没有指定基类,python的类就会默认继承object类,object是所有python类的基类,它提供了一些常用的方法(如__str__)的实现。
>>>ParentClass1.__bases__
(<class 'object'>,)
继承与抽象(先抽象再继承)
继承描述的是子类与父类之间的关系,要找出这种关系,必须先抽象再继承
继承与重用性
在开发过程中,如果我们定义了一个类A,然后有想新建立另外一个类B,但是类B的大部分内容与类A的相同时 我们不可能从头开始写一个类B,这就用到了类的继承的概念
通过继承的方式新建类B,让B继承A,B会遗传A的所有属性 (数据属性和函数属性),实现代码重用
class Hero:
def __init__(self,nickname,aggressivity,life_value):
self.nickname=nickname
self.aggressivity=aggressivity
self.life_value=life_value
def move_forward(self):
print('%s move forward' %self.nickname)
def move_backward(self):
print('%s move backward' %self.nickname)
def move_left(self):
print('%s move forward' %self.nickname)
def move_right(self):
print('%s move forward' %self.nickname)
def attack(self,enemy):
enemy.life_value-=self.aggressivity
class Garen(Hero):
pass
class Riven(Hero):
pass
g1=Garen('草丛伦',100,300)
r1=Riven('锐雯雯',57,200)
print(g1.life_value)
r1.attack(g1)
print(g1.life_value)
'''
运行结果
243
'''
象g1.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找 直到最顶级的父类
派生
子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响父类),需注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了
class Riven(Hero):
camp='Noxus'
def attack(self,enemy): 在自己这里定义新的attack,不再使用父类的attack,且不会影响父类
print('from riven')
def fly(self): 在自己这里定义新的
print('%s is flying' %self.nickname)
在子类中,新建的重名的函数属性,在编辑函数你没功能的时候,有可能需要重用父类中重名的那个函数功能,应该时用调用普通函数的方式,即 类名.func(),此时就与调用普通函数无异了,因此即便时self参数也要为去传值
class Riven(Hero):
camp='Noxus'
def __init__(self,nickname,aggressivity,life_value,skin):
Hero.__init__(self,nickname,aggressivity,life_value) 调用父类功能
self.skin=skin 新属性
def attack(self,enemy): 在自己这里定义新的attack, 不再使用父类的attack,且不会影响父类
Hero.attack(self,enemy)调用功能
print('from riven')
def fly(self): 在自己这里定义新的
print('%s is flying' %self.nickname)
r1.Riven('锐雯‘,57,200,'比基尼’)
r1.fly()
print(r1.skin)
'''
运行结果
锐雯 is flying
比基尼
‘’‘
组合与重用
软件重用的重要方式除了继承之外还有另外一种方式,即 :组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
class Equip:武器装备类
def fire(self):
print('release Fire skill')
class Riven: 英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类
camp='Noxus'
def __init__(self,nickname):
self.nickname=nickname
self.equip=Equip() 有Equip类产生一个装备,赋值给实例的equip属性
r1=Riven('锐雯’)
r1.equip.fire() 可以使用组合的类产生的对象所持有的方法
release Fire skill
组合与继承都是有效地利用已有类的资源的重要方式,但是两者的概念和使用场景皆不同
1 继承方式
通过继承建立派生类与基类之间的关系,他是一种 是 的关系,当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,
2 组合的方式
用组合的方式建立了类与组合的类之间的关系,他是一种 有 的关系,比如教授有生日 教授教授python和linux课程
class People:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
class Course:
def __init__(self,naem,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 Teacher(People):
def __init__(self,name,age,sex,job_title)
People.__init__(self,name,age,sex)
self.job_title=job_title
self.course=[]
self.students=[]
class Student(People):
def __init__(self,name,age,sex):
People__init__(self,name,age,sex)
self.course=[]
egon=Teacher('egon',18,'male',沙河霸道金牌讲师‘)
s1=Student('牛榴弹’,18,’female')
python=Course('python','3mos',3000.0)
linux=Course('python',3mos',3000.0)
为老师egon和学生s1添加课程
egon.course.append(python)
egon.course.append(linux)
s1.course.append(python)
为老师添加学生s1
egon.students.appeng(s1)
使用
for obj in egon.course:
obj.tell_info()
当类之间有显著不同,并且较小的类是较大的类所需要的组合时,用组合较好
接口与归一化设计
接口提取一群类的共同函数,可以把接口当作哟个函数的集合
然后让子类去实现接口的函数
这么做的意义在与归一化,归一化就是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都是一样
好处:归一化时使用者无需管线对象的类时什么,只需要的直到这些对象具备某些功能就可以了,这极大地降低了使用者的使用难度
归一化使高层的外部使用者可以不加区别的处理所有接口兼容的对象集合
模仿interface
在python中根本就没有一个叫interface的关键词,如果非要去模仿接口概念可以记住第第三方模块 zope.interface
也可以使用继承
继承的两种用途
1 继承基类的方法,并且做出自己的改变或者扩展(代码重用)实践中,继承的这中用途意义并不大,甚至时有害的,一位它使得子类与基类出现强耦合
2 生命某子类兼容于某基类,定义一个接口类(模仿interface),接口类中定义了一些几口名(就是函数名)并且未实现接口的功能,子类继承接口类,并且实现接口中的功能
class Interface : 定义接口Interface类模仿接口的概念,python中压根没interface关键字来顶一个接
def read(self):定义接口函数read
pass
def write(self): 定义接口函数write
pass
class Txt(Interface):文本,具体实现read和write
def read(self):
print('文本数据的读取方法‘)
def write(self):
print('文本数据的读取方法’)
class Sata(Interface):磁盘,具体实现read和write
def read(self):
print('硬盘数据读取方法‘)
def write(self):
print('硬盘数据的读取方法’)
class Process(Interface):
def read(self):
print('进程数据的读取方法‘)
def write(self):
print('进程数据读取的方法’)
上面的代码只是看起来象接口,其实并没有起到接口的作用,子类完全可以不用去实现接口,这就用到了抽象类
抽象类
1 什么时抽象类
与java一样,python也是抽象类的概念但是同时需要借助模块实现,抽象类时一个特殊的类,它的特殊之处在于只能被继承,不被实例化
2 为什么要有抽象类 如果说是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性
3 在python中实现抽象类
#_*_coding:utf-8_*_
__author__='Linhaifeng'
#一切皆文件
import abc 利用abc模块实现抽象类
class All_file(metaclass=abc.ABCMeta):
all_type='file'
@abc.abstractmethod 定义抽象方法,无需实现功能
def read(self):
'子类必须定义读功能‘
pass
@abc.abstractmethod 定义抽象方法,无需实现功能
def write(self):
'子类必须定义写功能’
pass
class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('文本数据的读取方法')
def write(self):
print('文本数据的读取方法')
class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('硬盘数据的读取方法')
def write(self):
print('硬盘数据的读取方法')
class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('进程数据的读取方法')
def write(self):
print('进程数据的读取方法')
wenbenwenjian=Txt()
yingpanwenjian=Sata()
jinchengwenjian=Process()
这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()
print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)
4 抽象类与接口
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read.write),而接口只强调函数属性的相似性
抽象类是一个介于类和接口直接的概念,同时具备类和接口的部分特性,可以用来实现归一化设计
继承实现的原理(可恶的菱形问题)
1 继承顺序
在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)
如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找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):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D,E):
# def test(self):
# print('from F')
pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性
#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类
继承顺序
2 继承原理(python中如何实现的继承)
对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个的属性的类为止
而这个MRO列表的构造是通过一个C3线性算法实现的,它实际就是合并父类的MRO列表并遵循如下三条准则:
1 子类会先于父类被检查
2 多个父类会根据它们在列表中的顺序被检测
3 如果对下一个类存在两个合法选择,选择第一个父类
子类中调用父类的方法
方法一 指名道姓,即父类名.父类方法()
class Vehicle: 定义交通工具
Country='China'
def __init__(self,name,speed,load,power):
self.name=name
self.speed=speed
self.load=load
self.power=power
def run(self):
print('开动了。。')
class Subway(Vehicle): 地铁
def __init__(self,name,speed,load,power,line):
Vehicle.__init__(self,name,speed,load,power)
self.line=line
def run(self):
print('地铁%s号线欢迎您‘ %self.line)
Vehicle.run(self)
linel3=Subway('中国铁路’,‘180m/s’,‘1000人/箱’,‘电’,l3)
linel3.run()
方式二 super()
class Vehicle: #定义交通工具类
Country='China'
def __init__(self,name,speed,load,power):
self.name=name
self.speed=speed
self.load=load
self.power=power
def run(self):
print('开动啦...')
class Subway(Vehicle): #地铁
def __init__(self,name,speed,load,power,line):
#super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
super().__init__(name,speed,load,power)
self.line=line
def run(self):
print('地铁%s号线欢迎您' %self.line)
super(Subway,self).run()
class Mobike(Vehicle):#摩拜单车
pass
line13=Subway('中国地铁','180m/s','1000人/箱','电',13)
line13.run()
强调 两者使用哪一种都可以,但最好不要混用
即使没有直接继承关系,super仍然会按照mro继续往后查找
#A没有继承B,但是A内super会基于C.mro()继续往后找
class A:
def test(self):
super().test()
class B:
def test(self):
print('from B')
class C(A,B):
pass
c=C()
c.test() #打印结果:from B
print(C.mro())
#[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
指名道姓与super()的区别
#指名道姓
class A:
def __init__(self):
print('A的构造方法')
class B(A):
def __init__(self):
print('B的构造方法')
A.__init__(self)
class C(A):
def __init__(self):
print('C的构造方法')
A.__init__(self)
class D(B,C):
def __init__(self):
print('D的构造方法')
B.__init__(self)
C.__init__(self)
pass
f1=D() #A.__init__被重复调用
'''
D的构造方法
B的构造方法
A的构造方法
C的构造方法
A的构造方法
'''
#使用super()
class A:
def __init__(self):
print('A的构造方法')
class B(A):
def __init__(self):
print('B的构造方法')
super(B,self).__init__()
class C(A):
def __init__(self):
print('C的构造方法')
super(C,self).__init__()
class D(B,C):
def __init__(self):
print('D的构造方法')
super(D,self).__init__()
f1=D() #super()会基于mro列表,往后找
'''
D的构造方法
B的构造方法
C的构造方法
A的构造方法
'''
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)