zoukankan      html  css  js  c++  java
  • python基础25——继承&属性查找&多继承的菱形问题&Mixins机制

    继承

    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)



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

    新式类:继承了object类的子类,以及该子类的子类子子类。。。
    经典:没有继承object类的子类,以及该子类的子类子子类。。。

    ps2:在python3中没有继承任何类,那么会默认继承object类,所以python3中所有的类都是新式类
    print(Parent1.__bases__)
    print(Parent2.__bases__)


    III:python的多继承
    优点:子类可以同时遗传多个父类的属性,最大限度地重用代码
    缺点
    1、违背人的思维习惯:继承表达的是一种什么"是"什么的关系
    2、代码可读性会变差
    3、不建议使用多继承,有可能会引发可恶的菱形问题,扩展性变差,
    如果真的涉及到一个子类不可避免地要重用多个父类的属性,应该使用Mixins




    2、为何要用继承:用来解决类与类之间代码冗余问题





    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)



    示范2:基于继承解决类与类之间的冗余问题

    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)
    print(tea_obj.__dict__)
    print(tea_obj.school)

    tea_obj.score()









    属性的查找

    # 单继承背景下的属性查找
    示范一:
    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







    多继承带来的菱形问题

    一:菱形问题介绍与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列表

    obj = D()
    obj.test()

    print(D.test)

    print(C.mro()) # 类C以及类C的对象访问属性都是参照该类的mro列表
    c = C()
    c.test()


    # 总结:类相关的属性查找(类名.属性,该类的对象.属性),都是参照该类的mro






    二:如果多继承是非菱形继承,经典类与新式的属性查找顺序一样:
    都是一个分支一个分支地找下去,然后最后找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






    三:如果多继承是菱形继承,经典类与新式类的属性查找顺序不一样:
    经典类:深度优先,会在检索第一条分支的时候就直接一条道走到黑,即会检索大脑袋(共同的父类)
    新式类:广度优先,会在检索最后一条分支的时候检索大脑袋



    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() #



    总结:

    多继承到底要不用???
    要用,但是规避几点问题
    1、继承结构尽量不要过于复杂
    2、推荐使用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”关系。这个难题该怎么解决?

    ​ 不同的语言给出了不同的方法,让我们先来了解Java的处理方法。Java提供了接口interface功能,来实现多重继承:

    // 抽象基类:交通工具类
    public abstract class Vehicle {
    }
    
    // 接口:飞行器
    public interface Flyable {
        public void fly();
    }
    
    // 类:实现了飞行器接口的类,在该类中实现具体的fly方法,这样下面民航飞机与直升飞机在实现fly时直接重用即可
    public class FlyableImpl implements Flyable {
        public void fly() {
            System.out.println("I am flying");
        }
    }
    
    
    
    // 民航飞机,继承自交通工具类,并实现了飞行器接口
    public class CivilAircraft extends Vehicle implements Flyable {
        private Flyable flyable;
    
        public CivilAircraft() {
            flyable = new FlyableImpl();
        }
    
        public void fly() {
            flyable.fly();
        }
    }
    
    // 直升飞机,继承自交通工具类,并实现了飞行器接口
    public class Helicopter extends Vehicle implements Flyable {
        private Flyable flyable;
    
        public Helicopter() {
            flyable = new FlyableImpl();
        }
    
        public void fly() {
            flyable.fly();
        }
    }
    
    // 汽车,继承自交通工具类,
    public class Car extends Vehicle {
    }

    现在我们的飞机同时具有了交通工具及飞行器两种属性,而且我们不需要重写飞行器中的飞行方法,同时我们没有破坏单一继承的原则。飞机就是一种交通工具,可飞行的能力是飞机的属性,通过继承接口来获取。

    回到主题,Python语言可没有接口功能,但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,而不是一个飞行器。

    
    

    使用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


    ps:课外了解小知识
    Java只允许接口的多重继承。接口本质上是抽象基类,具有所有抽象方法,没有数据成员。
    java一样python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化,继承的子类必须实现抽象基类规定的方法,这样便可保证始终只有一个特定方法或属性的实现,并且不会产生歧义,因而也可以起到避免菱形问题的作用
    
    java的interfacehttps://www.cnblogs.com/linhaifeng/articles/7340153.html#_label6
    python的抽象基类https://www.cnblogs.com/linhaifeng/articles/7340153.html#_label7
    
    

     

     
  • 相关阅读:
    C++字节对齐与位域
    使用GDB调试将符号表与程序分离后的可执行文件
    在windows上编译apr库
    使用samba共享文件夹,提供给window访问
    Linux常用命令
    使用VS2015编译xlslib库
    VS资源收藏<持续更新中>
    使用Visual Studio 2017 C++17模块(module)特性
    RMAN中format的参数
    C#的Process类的一些用法
  • 原文地址:https://www.cnblogs.com/lucky-cat233/p/12668568.html
Copyright © 2011-2022 走看看