day 29 继承
1 继承
1.1 什么是继承
I:继承是一种创建新类的方式,新建的类可称为子类或派生类,父类又可称为基类或超类,子类会遗传父类的属性
II:需要注意的是:python 支持多继承,在 Python 中,新建的类可以继承一个或多个父类
class Parent1(object):
x=1111
class Parent2(object):
pass
class Sub1(Parent1): 单继承
pass
class Sub2(Parent1,Parent2): 多继承
pass
print(Sub1.__bases__)
print(Sub2.__bases__)
print(Sub1.x)
print(Parent1.__bases__)
print(Parent2.__bases__)
通过类的内置属性__ bases __可以查看类继承的所有父类
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
-
ps1: 在 python2 中有经典类与新式类之分
新式类:继承了 object 类的子类,以及该子类的子类子子类...
经典类:没有继承 object 类的子类,以及该子类的子类子子类...
-
ps2:在 python3 中没有继承任何类,那么会默认继承 object 类,所以 python3 中所有的类都是新式类
III:python 的多继承
-
优点:子类可以同时遗传多个父类的属性,最大限度地重用代码
-
缺点:
1 违背人的思维习惯:继承表达的是一种什么"是"什么的关系
2、代码可读性会变差
3 不建议使用多继承,有可能会引发可恶的菱形问题,扩展性变差,如果真的涉及到一个子类不可避免地要重用多个父类的属性,应该使用 Mixins
1.2 继承与抽象
要找出类与类之间的继承关系,需要先抽象,再继承。抽象即总结相似之处,总结对象之间的相似之处得到类,总结类与类之间的相似之处就可以得到父类,如下图所示

基于抽象的结果,我们就找到了继承关系

1.3 为何要用继承
用来解决类与类之间代码冗余问题
1.4 如何实现继承
-
示范 1:类与类之间存在冗余问题
class Student: school='OLDBOY' def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex def choose_course(self): print('学生%s 正在选课' %self.name) class Teacher: school='OLDBOY' def __init__(self,name,age,sex,salary,level): self.name=name self.age=age self.sex=sex self.salary=salary self.level=level def score(self): print('老师 %s 正在给学生打分' %self.name) -
示范 2:基于继承解决类与类之间的冗余问题
点击查看代码
class OldboyPeople: school = 'OLDBOY' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sexclass Student(OldboyPeople): def choose_course(self): print('学生%s 正在选课' % self.name) stu_obj = Student('lili', 18, 'female') print(stu_obj.__dict__) print(stu_obj.school) class Teacher(OldboyPeople): 老师的空对象,'egon',18,'male',3000,10 def __init__(self, name, age, sex, salary, level): 指名道姓地跟父类 OldboyPeople 去要__init__ OldboyPeople.__init__(self,name,age, sex) self.salary = salary self.level = level def score(self): print('老师 %s 正在给学生打分' % self.name) tea_obj=Teacher('egon',18,'male',3000,10) print(tea_obj.__dict__) print(tea_obj.school) tea_obj.score()
2 属性查找
2.1 单继承背景下的属性查找
有了继承关系,对象在查找属性时,先从对象自己的__ dict __中找,如果没有则去子类中找,然后再去父类中找……
-
示范一:
class Foo: def f1(self): print('Foo.f1') def f2(self): print('Foo.f2') self.f1() # obj.f1() class Bar(Foo): def f1(self): print('Bar.f1') obj=Bar() obj.f2() ------------------------ Foo.f2 Bar.f1 -
示范二:
class Foo: def f1(self): print('Foo.f1') def f2(self): print('Foo.f2') Foo.f1(self) # 调用当前类中的 f1 class Bar(Foo): def f1(self): print('Bar.f1') obj=Bar() obj.f2() ------------------------ Foo.f2 Foo.f1 -
示范三:父类如果不想让子类覆盖自己的方法,可以采用双下划线开头的方式将方法设置为私有的
class Foo: def __f1(self): # _Foo__f1 print('Foo.f1') def f2(self): print('Foo.f2') self.__f1() # self._Foo__f1 class Bar(Foo): def __f1(self): # _Bar__f1 print('Bar.f1') obj=Bar() obj.f2() # 在父类中找到f2方法,进而调用b._Foo__f1()方法,同样是在父类中找到该方法 ---------------------------- Foo.f2 Foo.f1
3 多继承带来的菱形问题
3.1 菱形问题介绍与 MRO
大多数面向对象语言都不支持多继承,而在Python中,一个子类是可以同时继承多个父类的,这固然可以带来一个子类可以对多个不同父类加以重用的好处,但也有可能引发著名的 Diamond problem菱形问题(或称钻石问题,有时候也被称为“死亡钻石”),菱形其实就是对下面这种继承结构的形象比喻

A类在顶部,B类和C类分别位于其下方,D类在底部将两者连接在一起形成菱形。
这种继承结构下导致的问题称之为菱形问题:如果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(C,B):
def test(self):
print('from D')
obj = D()
obj.test()
继承原理
python到底是如何实现继承的呢? 对于你定义的每一个类,Python都会计算出一个方法解析顺序(MRO:Method Resolution Order)列表,该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
ps:
1.由对象发起的属性查找,会从对象自身的属性里检索,没有则会按照对象的类.mro()规定的顺序依次找下去,
2.由类发起的属性查找,会按照当前类.mro()规定的顺序依次找下去,
总结:类相关的属性查找(类名.属性,该类的对象.属性),都是参照该类的 mro
3.2 非菱形继承查找
如果多继承是非菱形继承,经典类与新式的属性查找顺序一样:都是一个分支一个分支地找下去,然后最后找 object

点击查看代码
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')
新式类
print(A.mro()) A->B->E->C->F->D->object
obj = A()
obj.test() 结果为:from F
3.3 菱形继承查找
如果多继承是菱形继承,经典类与新式类的属性查找顺序不一样:
经典类:深度优先,会在检索第一条分支的时候就直接一条道走到黑,即会检索大脑袋(共同的父类)
新式类:广度优先,会在检索最后一条分支的时候检索大脑袋


点击查看代码
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')
----------------------------------------------------
新式类
print(A.mro()) A->B->E->C->F->D->G->object
经典类:A->B->E->G->C->F->D
obj = A()
obj.test()
总结:
多继承到底要不用???
要用,但是规避几点问题
1、继承结构尽量不要过于复杂
2、推荐使用 mixins 机制:在多继承的背景下满足继承的什么"是"什么的关系
4 在子类派生的新方法中如何重用父类的功能
子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先于父类被查找,例如每个老师还有职称这一属性,我们就需要在Teacher类中定义该类自己的__ init __覆盖父类的
方式一:指名道姓
指名道姓调用某一个类下的函数,不依赖于继承关系
class OldboyPeople:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def f1(self):
print('%s say hello' %self.name)
class Teacher(OldboyPeople):
def __init__(self,name,age,sex,level,salary):
OldboyPeople.__init__(self,name,age,sex)
self.level = level
self.salary=salary
tea_obj=Teacher('egon',18,'male',10,3000)
print(tea_obj.__dict__)
方式二:super()
super()调用父类提供给自己的方法,严格依赖继承关系。调用 super()会得到一个特殊的对象,该对象会参照发起属性查找的那个类的 mro,去当前类的父类中找属性
class OldboyPeople:
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def f1(self):
print('%s say hello' %self.name)
class Teacher(OldboyPeople):
def __init__(self,name,age,sex,level,salary):
super().__init__(name,age,sex) # 调用的是方法,自动传入对象
self.level = level
self.salary=salary
print(Teacher.mro())
tea_obj=Teacher('egon',18,'male',10,3000)
print(tea_obj.__dict__)
这两种方式的区别是:方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super()仍然会按照MRO继续往后查找
>>> #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
关于在子类中重用父类功能的这两种方式,使用任何一种都可以,但是在最新的代码中还是推荐使用super()
5 property装饰器
-
装饰器是在不修改被装饰对象源代码以及调用方式的前提下为被装饰对象添加新功能的可调用对象
-
property 是一个装饰器,是用来绑定给对象的方法伪造成一个数据属性
案例一:
-
成人的 BMI 数值:
过轻:低于 18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于 32
体质指数(BMI)=体重(kg)÷身高^2(m)
EX:70kg÷(1.75×1.75)=22.86
-
定义函数的原因 :
1 从 bmi 的公式上看,bmi 应该是触发功能计算得到的
2 bmi 是随着身高、体重的变化而动态变化的,不是一个固定的值说白了,每次都是需要临时计算得到的,但是 bmi 听起来更像是一个数据属性,而非功能
点击查看代码
class People: def __init__(self, name, weight, height): self.name = name self.weight = weight self.height = height @property def bmi(self): return self.weight / (self.height ** 2) # obj1 = People('egon', 70, 1.83) print(obj1.bmi()) obj1.height=1.86 print(obj1.bmi) # 类似于 print(obj1.bmi())
案例二:
点击查看代码
class People:
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
def set_name(self, val):
if type(val) is not str:
print('必须传入 str 类型')
return
self.__name = val
def del_name(self):
print('不让删除')
del self.__name
name=property(get_name,set_name,del_name)
obj1=People('egon')
print(obj1.get_name())
obj1.set_name('EGON')
print(obj1.get_name())
obj1.del_name()
人正常的思维逻辑
print(obj1.name)
obj1.name=18
del obj1.name
案例三:class People:
点击查看代码
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, val):
if type(val) is not str:
print('必须传入 str 类型')
return
self.__name = val
@name.deleter
def name(self):
del obj1.name
print('不让删除')
del self.__name
obj1=People('egon')
人正常的思维逻辑
print(obj1.name)
obj1.name=18
del obj1.name
6 Mixin机制
-
多继承的正确打开方式:mixins 机制
mixins 机制核心:就是在多继承背景下尽可能地提升多继承的可读性
ps:让多继承满足人的思维习惯=》什么"是"什么
补充:通常 Mixin 结果的类放在左边
class Vehicle: pass class FlyableMixin: #这种机制只是一种命名习惯而已 def fly(self): pass class CivilAircraft(FlyableMixin,Vehicle): # 民航飞机 pass class Helicopter(FlyableMixin,Vehicle): # 直升飞机 pass class Car(Vehicle): # 汽车并不会飞,但按照上述继承关系,汽车也能飞了 pass
7 绑定方法与非绑定方法
7.1 绑定方法
特殊之处在于将调用者本身当做第一个参数自动传入
- 绑定给对象的方法:调用者是对象,自动传入的是对象
- 绑定给类的方法:调用者类,自动传入的是类
点击查看代码
import settings
class Mysql:
def __init__(self,ip,port):
self.ip=ip
self.port=port
def func(self):
print('%s:%s' %(self.ip,self.port))
@classmethod # 将下面的函数装饰成绑定给类的方法
def from_conf(cls):
print(cls)
return cls(settings.IP, settings.PORT)
obj1=Mysql('1.1.1.1',3306)
obj2=Mysql.from_conf()
print(obj2.__dict__)
7.2 非绑定方法
静态方法:
没有绑定给任何人:调用者可以是类、对象,没有自动传参的效果
点击查看代码
class Mysql:
def __init__(self,ip,port):
self.nid=self.create_id()
self.ip=ip
self.port=port
@staticmethod # 将下述函数装饰成一个静态方法
def create_id():
import uuid
return uuid.uuid4()
@classmethod
def f1(cls):
pass
def f2(self):
pass
obj1=Mysql('1.1.1.1',3306)
print(Mysql.create_id)
print(obj1.create_id)
Mysql.create_id(1,2,3)
obj1.create_id(4,5,6)
print(Mysql.create_id)
print(Mysql.f1)
print(obj1.f2)

浙公网安备 33010602011771号