zoukankan      html  css  js  c++  java
  • 27-继承与派生

    一、继承的介绍

    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__)       # (<class '__main__.Parent1'>,)
    print(Sub2.__bases__)       # (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
    
    print(Sub1.x)               # 111

    ps1: 在python2中有经典类与新式类之分

    新式类:继承了object类的子类,以及该子类的子类子子类……

    经典:没有继承object类的子类,以及该子类的子类子子类……

    ps2:在python3中没有继承任何类,那么会默认继承object类,所以python3中所有的类都是新式类

    print(Parent1.__bases__)                # (<class 'object'>,)
    print(Parent2.__bases__)                # (<class 'object'>,)

    III:python的多继承

      优点:子类可以同时遗传多个父类的属性,最大限度地重用代码

      缺点:

        1、违背人的思维习惯:继承表达的是一种什么"是"什么的关系

        2、代码可读性会变差

        3、不建议使用多继承,有可能会引发可恶的菱形问题,扩展性变差,

      如果真的涉及到一个子类不可避免地要重用多个父类的属性,应该使用Mixins

    1.2 为何要继承

    用来解决类与类之间代码冗余问题

    1.3 如何实现继承

    示范一:类与类之间存在冗余问题

    # 示范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)

    示范二:基于继承解决冗余问题

    class OldboyPeople:
        school = 'OLDBOY'
    
        def __init__(self, name, age, sex):
            self.name = name
            self.age = age
            self.sex = sex
    
    
    class Student(OldboyPeople):
        def choose_course(self):
            print('学生%s 正在选课' % self.name)
    
    stu_obj = Student('lili', 18, 'female')
    print(stu_obj.__dict__)
    print(stu_obj.school)
    stu_obj.choose_course()
    
    
    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)
    
    tea_obj.score()

     二、继承下的属性查找

    2.1 单继承下的属性查找

    # 单继承背景下的属性查找
    # 示范一:
    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
    # Foo.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,# 调用当前类中的f1
    
    class Bar(Foo):
        def __f1(self): # _Bar__f1
            print('Bar.f1')
    
    obj=Bar()
    obj.f2()
    # Foo.f2
    # Foo.f1

    2.2 多继承下的属性查找

    多继承可分为菱形问题与非菱形问题

    2.2.1 mro方法

     在python中,新式类可以直接使用mro方法来查看mro列表,每个类均参照其mro列表来查看属性

    class A(object):
        # def test(self):
        #     print('from A')
        pass
    
    class B(A):
        def test(self):
            print('from B')
        pass
    
    class C(A):
        # def test(self):
        #     print('from C')
        pass
    
    class D(C,B):
        # def test(self):
        #     print('from D')
        pass
    
    print(D.mro()) # 类D以及类D的对象访问属性都是参照该类的mro列表
    # [<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
    
    # obj = D()
    # obj.test()
    
    # print(D.test)
    
    print(C.mro()) # 类C以及类C的对象访问属性都是参照该类的mro列表
    # [<class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
    # c=C()
    # c.test()
    
    # 总结:类相关的属性查找(类名.属性,该类的对象.属性),都是参照该类的mro

    2.2.2 非菱形多继承

    如果多继承是非菱形继承,经典类与新式的属性查找顺序一样:

        都是一个分支一个分支地找下去,然后最后找object

    class E:
        # def test(self):
        #     print('from E')
        pass
    
    class F:
        def test(self):
            print('from F')
    
    
    class B(E):
        # def test(self):
        #     print('from B')
        pass
    
    class C(F):
        # def test(self):
        #     print('from C')
        pass
    
    class D:
        def test(self):
            print('from D')
    
    
    class A(B, C, D):
        # def test(self):
        #     print('from A')
        pass
    
    # 新式类
    # print(A.mro()) # A->B->E->C->F->D->object
    
    obj = A()
    obj.test() # 结果为:from F

    2.2.3 菱形问题多继承

    如果多继承是菱形继承,经典类与新式类的属性查找顺序不一样:
    经典类:深度优先,会在检索第一条分支的时候就直接一条道走到黑,即会检索大脑袋(共同的父类)
    新式类:广度优先,会在检索最后一条分支的时候检索大脑袋
     
    class G: # 在python2中,未继承object的类及其子类,都是经典类
        # def test(self):
        #     print('from G')
        pass
    
    class E(G):
        # def test(self):
        #     print('from E')
        pass
    
    class F(G):
        def test(self):
            print('from F')
    
    class B(E):
        # def test(self):
        #     print('from B')
        pass
    
    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
    
    # 新式类
    # print(A.mro()) # A->B->E->C->F->D->G->object
    
    # 经典类:A->B->E->G->C->F->D
    obj = A()
    obj.test() 

     

    三、Python的mixins机制

    3.1 mixins机制介绍

    ​ 一个子类可以同时继承多个父类,这样的设计常被人诟病,一来它有可能导致可恶的菱形问题,二来在人的世界观里继承应该是个”is-a”关系。 比如轿车类之所以可以继承交通工具类,是因为基于人的世界观,我们可以说:轿车是一个(“is-a”)交通工具,而在人的世界观里,一个物品不可能是多种不同的东西,因此多重继承在人的世界观里是说不通的,它仅仅只是代码层面的逻辑。不过有没有这种情况,一个类的确是需要继承多个类呢?

    mixins机制核心:就是在多继承背景下尽可能地提升多继承的可读性(让多继承满足人的思维习惯=》什么"是"什么

    3.2 mixins一般使用

    民航飞机、直升飞机、轿车都是一个(is-a)交通工具,前两者都有一个功能是飞行fly,但是轿车没有,所以如下所示我们把飞行功能放到交通工具这个父类中是不合理的

    class Vehicle:  # 交通工具
        def fly(self):
            '''
            飞行功能相应的代码        
            '''
            print("I am flying")
    
    
    class CivilAircraft(Vehicle):  # 民航飞机
        pass
    
    
    class Helicopter(Vehicle):  # 直升飞机
        pass
    
    
    class Car(Vehicle):  # 汽车并不会飞,但按照上述继承关系,汽车也能飞了
        pass

    Python提供了Mixins机制,简单来说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语言惯用的手法),这个类是作为功能添加到子类中,而不是作为父类,它的作用同Java中的接口。所以从含义上理解,CivilAircraft、Helicopter类都只是一个Vehicle,而不是一个飞行器。

    3.3 mixins机制使用注意

    使用Mixin类实现多重继承要非常小心

    • 首先它必须表示某一种功能,而不是某个物品,python 对于mixin类的命名方式一般以 Mixin, able, ible 为后缀
    • 其次它必须责任单一,如果有多个功能,那就写多个Mixin类,一个类可以继承多个Mixin,为了保证遵循继承的“is-a”原则,只能继承一个标识其归属含义的父类
    • 然后,它不依赖于子类的实现
    • 最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了)

    Mixins是从多个类中重用代码的好方法,但是需要付出相应的代价,我们定义的Minx类越多,子类的代码可读性就会越差,并且更恶心的是,在继承的层级变多时,代码阅读者在定位某一个方法到底在何处调用时会晕头转向,如下

    class Displayer:
        def display(self, message):
            print(message)
    
    
    class LoggerMixin:
        def log(self, message, filename='logfile.txt'):
            with open(filename, 'a') as fh:
                fh.write(message)
    
        def display(self, message):
            super().display(message) # super的用法请参考下一小节
            self.log(message)
    
    
    class MySubClass(LoggerMixin, Displayer):
        def log(self, message):
            super().log(message, filename='subclasslog.txt') 
    
    
    obj = MySubClass()
    obj.display("This string will be shown and logged in subclasslog.txt")
    
    
    # 属性查找的发起者是obj,所以会参照类MySubClass的MRO来检索属性
    #[<class '__main__.MySubClass'>, <class '__main__.LoggerMixin'>, <class '__main__.Displayer'>, <class 'object'>]
    
    # 1、首先会去对象obj的类MySubClass找方法display,没有则去类LoggerMixin中找,找到开始执行代码
    # 2、执行LoggerMixin的第一行代码:执行super().display(message),参照MySubClass.mro(),super会去下一个类即类Displayer中找,找到display,开始执行代码,打印消息"This string will be shown and logged in subclasslog.txt"
    # 3、执行LoggerMixin的第二行代码:self.log(message),self是对象obj,即obj.log(message),属性查找的发起者为obj,所以会按照其类MySubClass.mro(),即MySubClass->LoggerMixin->Displayer->object的顺序查找,
    # 在MySubClass中找到方法log,开始执行super().log(message, filename='subclasslog.txt'),super会按照MySubClass.mro()查找下一个类,在类LoggerMixin中找到log方法开始执行,最终将日志写入文件subclasslog.txt

    3.4 总结

    问:多继承到底要不用?

    答:要用,但是规避几点问题

    1、继承结构尽量不要过于复杂

    2、推荐使用mixins机制:在多继承的背景下满足继承的什么"是"什么的关系

    四、派生与方法重用

    在子类派生的新方法中如何重用父类的功能?

    4.1 子类派生新方法中重用父类功能的两个方式

    方法一:“指名道姓”地调用某一个类的函数

    # 方式一:指名道姓调用某一个类下的函数=》不依赖于继承关系
    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()会得到一个特殊的对象,该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找

    # 方式二: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(Teacher,self).__init__(name,age,sex)  Python2中需要传入类名与self,python3中简化为无须传参
            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__)

    PS:在Python2中super的使用需要完整地写成super(自己的类名,self) ,而在python3中可以简写为super()

    4.2  super()的查找顺序

    两种方式的区别:方式一是跟继承没有关系的,而方式二的super()是依赖于继承的,并且即使没有直接继承关系,super()仍然会按照MRO继续往后查找

    # super()案例
    class A:
        def test(self):
            print('from A')
            super().test()
    
    class B:
        def test(self):
            print('from B')
    
    class C(A,B):
        pass
    
    obj=C()
    obj.test()              # from A   from B
    
    print(C.mro())          # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

    obj.test()首先找到A下的test方法,执行super().test()会基于MRO列表(以C.mro()为准)当前所处的位置继续往后查找(),然后在B中找到了test方法并执行。

    关于在子类中重用父类功能的这两种方式,使用任何一种都可以,但是在最新的代码中还是推荐使用super()

    五、 组合

    在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。不同的是:

    继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;

    组合则是一种“有”的关系,比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合,如下示例

    class Course:
        def __init__(self,name,period,price):
            self.name=name
            self.period=period
            self.price=price
        def tell_info(self):
            print('<%s %s %s>' %(self.name,self.period,self.price))
    
    class Date:
        def __init__(self,year,mon,day):
            self.year=year
            self.mon=mon
            self.day=day
        def tell_birth(self):
           print('<%s-%s-%s>' %(self.year,self.mon,self.day))
    
    class People:
        school='清华大学'
        def __init__(self,name,sex,age):
            self.name=name
            self.sex=sex
            self.age=age
    
    #Teacher类基于继承来重用People的代码,基于组合来重用Date类和Course类的代码
    class Teacher(People): #老师是人
        def __init__(self,name,sex,age,title,year,mon,day):
            super().__init__(name,age,sex)
            self.birth=Date(year,mon,day) #老师有生日
            self.courses=[] #老师有课程,可以在实例化后,往该列表中添加Course类的对象
        def teach(self):
            print('%s is teaching' %self.name)
    
    
    python=Course('python','3mons',3000.0)
    linux=Course('linux','5mons',5000.0)
    teacher1=Teacher('lili','female',28,'博士生导师',1990,3,23)
    
    # teacher1有两门课程
    teacher1.courses.append(python)
    teacher1.courses.append(linux)
    
    # 重用Date类的功能
    teacher1.birth.tell_birth()
    
    # 重用Course类的功能
    for obj in teacher1.courses: 
        obj.tell_info()

     此时对象teacher1集对象独有的属性、Teacher类中的内容、Course类中的内容于一身(都可以访问到),是一个高度整合的产物



  • 相关阅读:
    css变量
    es6的this指向
    Java面试题(包装类)
    moment笔记
    Class
    CSS斜切角
    Element.getBoundingClientRect()
    Do not mutate vuex store state outside mutation handlers.
    antd不想写那么多option怎么办
    解析URL参数
  • 原文地址:https://www.cnblogs.com/zhubincheng/p/12669333.html
Copyright © 2011-2022 走看看