zoukankan      html  css  js  c++  java
  • Python 面向对象 之 多继承 MRO

    Python 面向对象 之 多继承 MRO

    回顾 - 类继承

    关于子类的继承. 即子类可以继承父类的所有 方法及属性, 当子类要添加新功能或修改父类方法是, 可以在 子类对私进行 重写 overwrite . 大概回顾就行了, 这里也不想进行对过于基础性的问题进行探讨.

    class A:
        def __init__(self):
            self.a = 10
            self.b = 20
    
        @staticmethod
        def say_hello():
            print("hello, world!")
    

    在子类初始化的时候, 是需要手动调用父类的初始化方法来说明的, 语法为: 父类名.__ init__(self, ...)

    class B(A):
        def __init__(self, c, d):
            """重写父类__init__, 并新增d, e属性"""
            self.c = c
            self.d = d
            A.__init__(self)
            
            
            
    if __name__ == '__main__':
        b = B(4, 5)
        print(b.a)
        print(b.d)
    
        # output
        10
        5
    

    当然也可以这用 super 关键字

    class B(A):
        def __init__(self, c, d):
            """重写父类__init__, 并新增d, e属性"""
            self.c = c
            self.d = d
            super().__init__()
    

    多继承 - Bug

    这里主要演示一个, 多继承会出现的问题, 其实也不算bug吧, 原理我也没有仔细去推敲过哈, 先露出来看看吧.

    class 父亲:
        def __init__(self, name):
            self.name = name
            print("i am you father...")
    
    
    class 长子(父亲):
        def __init__(self, name, age):
            self.age = age
            父亲.__init__(self, name)
            print(" i am eldest son")
    
    class 次子(父亲):
        def __init__(self, name, gender):
            self.gender = gender
            父亲.__init__(self, name)
            print("i am the little son")
    
    class 长孙(长子, 次子):
        """收养的哦"""
        def __init__(self, name, age, gender):
            长子.__init__(self, name, age)
            次子.__init__(self, name, gender)
            print("i am the sweet heart")
    
    
    if __name__ == '__main__':
    
        king = 长孙("小王子", 23, "F")
    
    i am you father...
    i am eldest son
        
    i am you father...  # 多执行了一次 ???
    
    i am the little son
    i am the sweet heart
    

    卧槽. ....父类被执行了2次, 这感觉是浪费内存了呀, 嗯..有些紧张....

    为啥会出现调用2次父类呢? 或者 如何只让调用一次就好呢 ? 分析一波上面这一段代码

    长子.__init__(self, name, age) --> class 长子(父亲) --> class 父亲 -> print("father...")
    次子.__init__(self, name, gender) --> class 次子(父亲) --> class 父亲 -> print("father...")
    

    因此, 被调用了2次是没有问题的, 但这就带来一个新的优化问题: 浪费内存, 因为重复开辟空间了呀.

    更一般地描述, 即在多继承的情况下, 父类的属性可能会出现被多次执行的情况, 这里仅仅是一个简单的case, 如果非常复杂的结果, 则会浪费非常多的内存和反复调用.

    多继承 MRO

    MRO - 引入

    MRO 是 Pyhton 用来解决上面的这种, 会产生重复开辟空间的问题. 解决的办法是将 复杂结构上的所有类 全部映射到一个线性顺序上 (线性表), 这个顺序就是 MRO 顺序.

    MRO表, 事先就调用好了, 按表的顺序调用, 从右到左

    print(长孙.__mro__)
    
    # output
    
    (<class '__main__.长孙'>, <class '__main__.长子'>, <class '__main__.次子'>, <class '__main__.父亲'>, <class 'object'>)
    

    这个线性表, 我从执行的角度来看, 就是个 , 父类在栈底, 小王子在栈顶 (Top) , 而搜索的顺序, 兄弟们, 定睛一看, 这一层, 一层第搜索,.... 感觉这就是 树的广度优先算法 呀.

    MRO - super

    官方已经把这个问题给解决了, 方式就是内置方法 super()

    super() 方法在 Python 2 中是不一样的哦, Python3 更简洁, 直接拿父类的部件

    super() - 多继承

    class 父亲:
        def __init__(self, name):
            self.name = name
            print("i am you father...")
    
    # tips: 避免少接收参数, 则可都用上 *args, **kwargs 呀
    class 长子(父亲):
        def __init__(self, name, age, *args): # *args 接收 gender的.
            self.age = age
            super().__init__(name, *args)
            print(" i am eldest son")
    
    
    class 次子(父亲):
        def __init__(self, name, gender):
            self.gender = gender
            super().__init__(name)
            print("i am the little son")
    
    
    class 长孙(长子, 次子):
        """收养的哦"""
    
        def __init__(self, name, age, gender):
            super().__init__(name,  age, gender)
            print("i am the sweet heart")
    
    
    if __name__ == '__main__':
        king = 长孙("小王子", 23, "F")
        print(长孙.__mro__)
    
    
    i am you father...
    i am the little son
    i am eldest son
    i am the sweet heart
    
    (<class '__main__.长孙'>, <class '__main__.长子'>, <class '__main__.次子'>, <class '__main__.父亲'>, <class 'object'>)
    

    小结

    • 多继承会产生重复调用的问题, 浪费内存空间
    • MRO顺序是Python将类映射到一个线性表中来调用, 有序的, 能避免重复调用, 搜索算法目测是广度优先
    • super 的理解和用法, 注意的点是传参的数量问题, 不明白, 可以都为通用形式 (* args, **kwargs)

    还是稍微细节一波关于super(), 在单继承中, super().xxx 和 父类名. ... 二者是一样的效果, 但是在 多继承中, super() 使得父类方法只会执行一次, 传统的方法呢, 就出现了上面的那种 "钻石调用问题". 在使用 super()时候, 必须将参数全部传递, tips, 后面加上 * args * *kwargs.

    从最初的例子看, 在单继承的时候呢, super()方法只能传递父类方法所需的参数, 似乎又不能 *args ** kwargs , 总是感觉这些设计, 搞得怪怪的, 都要 debug很久.多继承中呢, 一般用的比较多的是 **重写或继承 __ init__() **, 此处呢, super() 的优势 就显现出来了, 只要一行代码就可以将父类的都执行了, 嗯, 可能, 这就是 super() 要求传递所有参数的原因了吧.

    至于用类的方式, 还是用 super(), 我感觉, 我平时写的脚本, 基本都用不到继承哈哈, 真是有点尴尬, 感觉满身的技能, 却总是让我干一些, 打杂的活...知己难寻呀, 才无聊到写博客, 抄博客, 抄段子, 抄代码....

  • 相关阅读:
    web进修之—Hibernate 继承映射(5)
    web进修之—Hibernate 类型(4)
    web进修之—Hibernate 关系映射(3)
    web进修之—Hibernate起步(1)(2)
    poj2828(Buy Tickets)线段树
    hdu2795(Billboard)线段树
    hdu1394(Minimum Inversion Number)线段树
    hdu4407Sum(容斥原理)
    树的重心
    匈牙利算法
  • 原文地址:https://www.cnblogs.com/chenjieyouge/p/12246113.html
Copyright © 2011-2022 走看看