一、继承
新创建一个类,这个类可以继承一个或多个父类,新创建的类及哦啊做子类或派生类,被继承的类叫做父类或基类。
例:
class ParentClass_01: # 定义父类
pass
class ParentClass_02: # 定义父类
pass
class SonClass_01(ParentClass_01): # 继承一个父类
pass
class SonClass_02(ParentClass_01, ParentClass_02): # 继承多个父类
pass
通过类的内置属性__base__可以查看类继承的所有父类
print(ParentClass_01.__bases__)
# (<class 'object'>,)
print(SonClass_02.__bases__)
# (<class '__main__.ParentClass_01'>, <class '__main__.ParentClass_02'>)
二、继承与抽象
要找出类与类之间的继承关系,需要先抽象,再继承。抽象就是总结相似之处,总结对象之间的相处之处得到类,总结类与类之间的相似之处就可以得到父类。如图
基于抽象的结果,可以找到继承的关系
由上图我们可以看出类与类之间的继承指的是:什么‘是’什么的关系(比如:人类、猪类、熊猫类都是动物类)。
子类可以继承父类的所有属性,因此继承用来解决类与类之间的代码重用性。
例如:定义一个Student类,一个Teacher类
class Student:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def choose_course(self):
print(f'{self.name} is choosing a course')
class Teacher:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def teaching(self):
print(f'{self.name} is teaching')
Student类与Teacher类之间存在重复的代码,老师与学生都是人类,所以可以定义一个People类,实现代码重用。
class People:
school = '四川大学'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
class Student(People):
def choose_course(self):
print(f'{self.name} is choosing a course')
class Teacher(People):
def teaching(self):
print(f'{self.name} is teaching')
Student类与Teacher类中没有定义__init__方法,会从父类中找到__init__方法,从而进行正常实例化。
teacher = Teacher('zhang', 24, 'male')
print(teacher.school, teacher.name,teacher.age, teacher.sex)
# 四川大学 zhang 24 male
三、属性查找
有了继承关系,对象在查找属性时,先从对象自己的__dict__中查找,如果没有则去子类中找,然后再去父类中找。
class Foo:
def f1(self):
print('from Foo.f1')
def f2(self):
print('from Foo.f2')
self.f1()
class Bar(Foo):
def f1(self):
print('from Bar.f1')
b = Bar()
b.f2()
# from Foo.f2
# from Bar.f1
查找顺序:b.f2()会现在子类Bar中找f2,没有找到,再去父类Foo中找到f2,先打印from Foo.f2,然后执行到self.f1(),即b.f1(),仍会按照:对象本身->类Bar->父类Foo的顺序依次找下去,在类Bar中找到f1,因而打印结果为from Bar.f1。
父类如果不想让子类覆盖自己的方法,可以采用双下划线的方式将方法设置为私有的:
class Foo:
def __f1(self): # 变形为:Foo__f1
print('from Foo.f1')
def f2(self):
print('from Foo.f2')
self.__f1() # 变形为:self.Foo__f1,因此只会调用自己所在的类中的方法
class Bar(Foo):
def f1(self):
print('from Bar.f1')
b = Bar()
b.f2()
# from Foo.f2
# from Foo.f1
四、继承的先后顺序
一个子类可以同时继承多个父类,那么在查找元素属性的时候,是根据什么方式来进行的。
查找方式分为:深度优先和广度优先
新式类采用广度优先,经典类采用深度优先
先来了解一下什么是新式类,什么是经典类
在python2中才有经典类与新式类的说法,在python3中只有新式类。
新式类:由任意内置类型派生出来的类,如:str,list,dic,object
经典类:不是由任意内置类型派生出来的类
# 在python2中
class A(object): # 新式类
pass
class B: # 经典类
pass
class C: # 经典类
pass
class D(C): # 经典类
pass
# 在python3中
class A: # 新式类,继承了object类
pass
print(A.__bases__)
# (<class 'object'>,)
例:
class P1:
def foo(self):
print('P1---foo')
class P2:
def foo(self):
print('P2---foo')
def bar(self):
print('P2---bar')
class C1(P1, P2):
pass
class C2(P1, P2):
def bar(self):
print('C2---bar')
class D(C1, C2):
pass
d = D()
d.foo()
d.bar()
继承关系:
在python2中,使用深度优先查找,查找顺序为:
D->C1->P1->P2->C2->P1->P2
输出结果为:
P1---foo
P2---bar
在python3中,使用广度优先查找,查找顺序为:
D->C1->C2->P1->P2
输出结果为:
P1---foo
C2---bar
五、派生与方法重用
子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找,例如每个老师还有职称这一属性,我们就需要在Teacher类中定义该类自己的__init__覆盖父类的
class People:
school = '四川大学'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People):
def __init__(self, name, age, sex, title):
self.name = name
self.age = age
self.sex = sex
self.title = title
def teaching(self):
print(f'{self.name} is teaching')
teacher = Teacher('lili',18,'female','特级讲师')
print(teacher.name, teacher.age, teacher.sex, teacher.title)
# lili 18 female 特级讲师
子类的__init__中有重复代码,可以使用下面的方式实现在子类派生出的方法中重用父类的功能。
1:“指名道姓”地调用某一个类的函数
class People:
school = '四川大学'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People):
def __init__(self, name, age, sex, title):
People.__init__(self,name, age, sex) # 调用的是函数,需要传入self
self.title = title
def teaching(self):
print(f'{self.name} is teaching')
2:使用super()
调用super()会得到一个特殊的对象,该对象专门用来引用父类的属性。
class People:
school = '四川大学'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People):
def __init__(self, name, age, sex, title):
super().__init__(name, age, sex) # 调用的是绑定方法,自动传入self
self.title = title
def teaching(self):
print(f'{self.name} is teaching')
使用这两种方法时,需要注意传参的差别。这两种方式的区别是:第一种是跟继承没有关系的,第二种的super()是依赖于继承的,并且即使没有直接继承关系,super()也会按照MRO继续往后找。