单继承(派生,super应用) 多继承下的属性查找顺序 mixins机制 组合

派生

继承了父类的功能后,子类又定义了新的功能这种形式称为派生

方式一:指名道姓地引用某一类的函数,与继承无关!!!
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列表并遵循如下三条准则:

  1. 子类会先于父类被检查
  2. 多个父类会根据它们在列表中的顺序被检查
  3. 如果下一个类存在两个合法选择,选择第一个父类

参照下述代码,多继承结构为非菱形结构,此时,会按照先找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()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值