派生
继承了父类的功能后,子类又定义了新的功能这种形式称为派生
方式一:指名道姓地引用某一类的函数,与继承无关!!!
class People:
school = "虹桥校区"
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Student(People):
def choose(self):
print("%s 选课成功" % self.name)
stu1 = Student("nana", 18, "female") # Student调用父类实例化对象stu1
stu1.choose()
class Teacher(People):
def __init__(self, name, age, gender, level):
People.__init__(self, name, age, gender) # 类调类的功能属性,相当于调普通函数,正常传参
self.level = level
def score(self):
print("%s 正在为学生打分" % self.name)
tea1 = Teacher("biu", 19, "male", 20) # Teacher调用父类实例化对象tea1
tea1.score()
单继承背景下
mro()会显示类的继承顺序,mor列表会按照从左到右开始查找父类,直到找到第一个匹配这个属性的类为止,类.mro()查看mro列表
class C:
x = 222
class B(C):
pass
class A(B):
pass
obj = A()
print(obj.x)
print(A.mro()) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]
print(B.mro()) # [<class '__main__.B'>, <class '__main__.C'>, <class 'object'>]
super应用
super()会返回一个特殊的对象,该对象会参考发起属性查找的那一个类的mro列表,依次去当前类的父类中查找属性。
方式二:
super() "找爸爸要",与继承有关!!!
class People:
school = "虹桥校区"
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Teacher(People):
def __init__(self, name, age, gender, level): # 形参
super().__init__(name, age, gender) # super()会返回一个特殊的对象,该对象会参考发起属性查找的那一个类的mro列表,依次去当前类的父类中查找属性(super(Teacher,self).__init__(name, age, gender) python2中的用法)
self.level = level
def score(self):
print("%s 正在为学生打分" % self.name)
tea1 = Teacher("biu", 19, "male", 20)
print(tea1.__dict__) # {'name': 'biu', 'age': 19, 'gender': 'male', 'level': 20}
多继承背景下
菱形继承
大多数面向对象语言都不支持多继承,而在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(B,C):
pass
obj = D()
obj.test() # 结果为:from B
新式类内置了mro方法可以查看线性列表的内容,经典类没有该内置该方法
print(D.mro()) # mro列表
# [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
obj.test()的查找顺序是,先从对象obj本身的属性里找方法test,没有找到,则参照属性的发起者(即obj)所处类D的MRO列表来依次检索,首先在类D中未找到,然后在B中找到方法test。
python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。 而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果下一个类存在两个合法选择,选择第一个父类
参照下述代码,多继承结构为非菱形结构,此时,会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性
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')
pass
print(A.mro())
'''
[<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 B
# 可依次注释上述类中的方法test来进行验证
深度优先与广度优先
经典类:深度优先
如果没有关系为菱形结构,那么经典类与新式类会有不同的mro,分别对应属性的两种查找方式:深度优先和广度优先
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')
pass
obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->G->C->F->D->object
# 可依次注释上述类中的方法test来进行验证,注意请在python2.x中进行测试
新式类:广度优先
class G(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')
pass
obj = A()
obj.test() # 如上图,查找顺序为:obj->A->B->E->C->F->D->G->object
# 可依次注释上述类中的方法test来进行验证
案例:
class A:
def test(self):
print("from A")
super().test()
class B:
def test(self):
print("from B")
class C(A, B): # 查找顺序[C,A,B,object]
pass
总结:
python3里面继承属性的查找顺序,以属性发起者优先,找不到会从发起者的父类里面去找属性,
如果父类里面没有,会从第二个父类里面查找,应当遵循广度优先的原则
mixins机制
mixins机制:多继承的命名规范
一个字类可以同时继承多个父类,这样的设计常被人诟病,一来它有可能导致可恶的菱形问题,二来在人的世界观里面继承应该是个“is-a”的关系。比如轿车类之所以可以继承交通工具类,是因为基于人的世界观,我们可以说:轿车是一个(“is-a”)交通工具,而在人的世界观里面,一个物品不可能是多种不同的东西。因此多重继承在人的世界观里是说不通的,它仅仅是代码层面的逻辑。不过有没有这种情况,一个类的确是需要继承多个类呢?
答案是有,我们还是可以拿交通工具举例子:
民航飞机、直升飞机、轿车都是一个(is-a)交通工具,前两者都有一个功能是飞行fly,但是轿车没有,所以如下所示我们把飞机功能放到交通工具这个父类下是不合理的。
代码呈现
class Vehicle: # 交通工具
def fly(self):
'''
飞行功能相应的代码
'''
print("I am flying")
class CivilAircraft(Vehicle): # 民航飞机
pass
class Helicopter(Vehicle): # 直升飞机
pass
class Car(Vehicle): # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
pass
但是如果民航飞机和直升机都各自写自己的飞行fly方法,又违背了代码尽可能重用的原则(如果以后飞行器越来越多,那么重复代码会越来越多。为了尽可能地重用代码,那就只好在定义出一个飞行器的类,然后让民航飞机和直升飞机同时继承交通工具以及飞行器两个父类,这样就出现了多重继承。这时又违背了继承必须是”is-a”关系。
简单来说Mixins机制指的是子类混合(mixin)不同类的功能,而这些类采用统一的命名规范(例如Mixin后缀),以此标识这些类只是用来混合功能的,并不是用来标识子类的从属"is-a"关系的,所以Mixins机制本质仍是多继承,但同样遵守”is-a”关系,如下
class Vehicle: # 父类交通工具
pass
class FlyableMixin: # 父类飞行功能
def fly(self):
'''
飞行功能相应的代码
'''
print("I am flying")
class CivilAircraft(FlyableMixin, Vehicle): # 民航飞机
pass
class Helicopter(FlyableMixin, Vehicle): # 直升飞机
pass
class Car(Vehicle): # 汽车
pass
# ps: 采用某种规范(如命名规范)来解决具体的问题是python惯用的套路
该代码可以理解成:民航飞机与直升飞机都是交通工具,具备飞行相关的功能
可以看到,上面的CivilAircraft、Helicopter类实现了多继承,不过它继承的第一个类我们起名为FlyableMixin,而不是Flyable,这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类,表示混入(mix-in),这种命名方式就是用来明确地告诉别人(python语言惯用的手法),这个类是作为功能添加到子类中,而不是作为父类。所以从含义上理解,CivilAircraft、Helicopter类都只是一个Vehicle,而不是一个飞行器。
组合
如果类与类,对象与对象之间的关系是 “xx是xx” ,那么我们就可以定义子类与父类,对象与类的关系来表达这个关系。如果类与类,对象与对象之间的关系 “不是xx是xx的关系” ,是其他的关系,就需要用到组合的关系。给某一个对象添加属性,充分利用面向对象编程的优点,扩展性强。
class Abb:
school = "虹桥校区"
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Student(Abb):
def choose(self):
print("%s 选课成功" % self.name)
class Teacher(Abb):
def __init__(self, name, age, gender, level):
Abb.__init__(self, name, age, gender)
self.level = level
def score(self):
print("%s 正在为学生打分" % self.name)
class Course:
def __init__(self, name, price, period):
self.name = name
self.price = price
self.period = period
def tell(self):
print("课程信息:%s %s %s" % (self.name, self.price, self.period))
python = Course("python", 20000, "6mons") # 调用类(Course)实例化得到对象python
linux = Course("linux", 20000, "5mons")
stu1 = Student("lili", 18, "female") # 调用类Student(父类功能)实例化得到对象stu1
stu2 = Student("nana", 16, "female")
stu3 = Student("dada", 19, "male")
tea1 = Teacher("egon", 18, "female", 10) # 调用类(Teacher)实例化得到对象tea1
tea2 = Teacher("biu", 19, "male", 20)
# stu1.course = python # python = stu1.course 修改变量名,将课程python数据属性伪装成stu1.course功能属性
# stu1.course.tell() # 等同于python.tell
stu1.courses = [] # 将学生1的课程名称定义成一个列表
stu1.courses.append(python) # python = {Course.name:"python",Course.price:20000,Course.period:"6mons"}
stu1.courses.append(linux)
for course_obj in stu1.courses: # 往列表里面添加两个课程对象,通过for循环取出
course_obj.tell() # for循环取出后直接运行调用函数tell(),等同于for循环出对象python和linux去调用tell()