继承&派生
什么是继承
继承指的是类与类之间的关系,是一种"父子关系",在Py中,继承是一种创建新类的方式
新建的类可以继承一个或多个父类,新建的类称为子类或派生类,父类又称为基类或超类
子类可以继承父类的特征属性和方法属性,所以说,继承的功能之一就是解决代码重用问题
单继承&多继承
单继承:子类只继承一个父类
多继承:子类继承多个父类
class ParentClassA: # 定义父类
pass
class ParentClassB: # 定义父类
pass
class SubClass1(ParentClassA): # 单继承,定义子类时把父类名放在括号中即可
pass
class SubClass2(ParentClassA, ParentClassB): # 多继承,继承时,多个父类用逗号隔开
pass
查看继承的方法
类名.__base__ :只查看从左到右继承的第一个类
类名.__bases__ :查看所有继承的父类
print(SubClass1.__base__)
print(SubClass2.__base__)
print(SubClass1.__bases__)
print(SubClass2.__bases__)
输出结果:
<class 'main.ParentClassA'>
<class 'main.ParentClassA'>
(<class 'main.ParentClassA'>,)
(<class 'main.ParentClassA'>, <class 'main.ParentClassB'>)
经典类&新式类
在Py2中类分经典类和新式类,在Py3中所有的类同一默认为新式类
Py2中经典类:没有显示继承object类的类,以及该类的子类,都是经典类
Py2中新式类:显示的声明继承object类的类,以及该类的子类,都是新式类
Py3中无论是否继承object类,都默认继承object类
- 如果没有指定基类,Py3会默认继承object类,object类提供常见方法(如,__str__)
ParentClassA.__bases__ # 打印 (<class 'object'>,)
先抽象再继承
抽象即抽取类似或者说比较像的部分,抽象的作用是划分类别(隔离关注点,降低复杂度)
继承是基于抽象的结果,通过编程语言去实现它,经历抽象这个过程,才能通过继承的方式去表达抽象的结果
抽象只是分析和设计的过程中,一个动作或一个技巧,通过抽象分析得到程序中的类
属性查找顺序
对象访问其属性时,先在对象本身的命名空间找,若找不到;再到其类的命名空间找,若找不到;再到父类中找,直到最顶级父类,如果都找不到,则报错。
分析:
class Foo:
def f1(self):
print('Foo.f1')
def f2(self):
print('Foo.f2')
self.f1()
class Bar(Foo):
def f1(self):
print('Bar.f1')
b=Bar()
b.f2()
# 打印结果:
# Foo.f2
# Bar.f1
- 对象b发起的访问其属性f2(),首先其本身没有,再在其类Bar中找,找不到;再到其父类Foo中查找,找到f2();此时即开始执行函数f2(),先打印"Foo.f2",再调用self.f1()。此时,self就是对象b,故执行self.f1()就等于是执行b.f1(),所以又开始现在b中找f1,找不到;再在b的类Bar中找,找到,于是打印"Bar.f1"
继承的实现原理
继承实现原理:定义一个类,python会计算出一个方法解析顺序(MRO)列表,这个列表就是一个简单的所有父类的线性顺序列表。通过 "类名.mro()" 查看,等同于调用 "类名.__mro__"
Py调用查找属性时:会在MRO列表上从左到右开始查找父类,直到找到第一个匹配这个属性的类为止。
属性查找:
- 对于单继承的类,就是按照继承的一条直线来找;
- 对于多继承的类,就按照MRO列表的顺序来找;
Py中,对于多继承的查找,按照MRO列表顺序查找。经典类和新式类是有区别的。
- 经典类按照 深度优先的原则
- 新式类按照 广度优先的原则
派生
子类可以定义自己属性,当与父类的属性相同时,不会影响父类
注意,一旦重新定义了子类的属性且与父类的重名时,子类及其对象调用该属性时,就以子类的为准
子类中,在新建与父类重名的方法属性时,可以既重用父类的那个方法属性,又加上自己的新功能。
此时,可以使用普通函数的调用方式,即 类名.func(),此时与调用普通函数无异,需要手动传参(包括self)
class Hero:
def __init__(self,nickname,aggressivity,life_value): # 父类方法属性
self.nickname=nickname
self.aggressivity=aggressivity
self.life_value=life_value
class Riven(Hero):
camp='Noxus'
def __init__(self,nickname,aggressivity,life_value,skin):
Hero.__init__(self,nickname,aggressivity,life_value) # 调用父类功能
self.skin=skin # 新属性
r1=Riven('锐雯雯',57,200,'比基尼') # 子类对象初始化
子类调用父类的方法
子类派生的新方法中,往往需要重用父类的方法,有两种实现方式:
方式一:指名道姓,即 父类名.父类方法()
- 就是上面代码示例中的方式
- 此方式不依赖继承关系;其实就是调用普通函数的,与继承无关,需要手动传参。
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) # "指名道姓" 调用父类方法
方式二:super()
- super().属性([参数])或 super(当前的类名,self).属性([参数])
- super方式依赖继承关系;super在MRO列表
class Subway(Vehicle): #地铁
def __init__(self,name,speed,load,power,line):
#super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
super().__init__(self,name,speed,load,power)
self.line=line
def run(self):
print('地铁%s号线欢迎您' %self.line)
super(Subway,self).run() # 等同于 super().run()
并且就算没有继承关系,super依然会按照MRO列表继续往后查找
基于谁的查找,遇到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'>]
组合
组合的定义
组合指的是,一个类使用另一个类的对象作为自己的特征属性,即类的组合
组合反映的也是类与类之间的关系,是一种"有"的关系
当类与类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较方便
class Student:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def tell_info(self):
return "%s-%s-%s" %(self.year, self.month, self.day)
stu1 = Student("小明", 10, "male")
d = Date(2010, 8, 8)
stu1.birthday = d
print(stu1.birthday.tell_info())