python super与__mro__

简介

由于在开发中对于多重继承中的super方法不是很熟悉,本文将介绍super的使用场景,及super是根据什么去查找方法。

super([type[, object-or-type]])

  • type:需要委托的父类或者兄弟类,就是调用哪个类,不写的话默认是父类
  • object-or-type:确定用于搜索的 method resolution order。 搜索会从 type 之后的类开始。直白点就是调用的顺序。举例来说,如果 object-or-type 的__mro__ 为 D -> B -> C -> A -> object 并且 type 的值为 B,则 `super()`` 将会搜索 C -> A -> object。
    object-or-type 的 mro 属性列出了 getattr() 和 super() 所共同使用的方法解析搜索顺序。 该属性是动态的,可以在任何继承层级结构发生更新的时候被改变。

官方术语:返回一个代理对象,它会将方法调用委托给 type 的父类或兄弟类。 这对于访问已在类中被重载的继承方法很有用。
正常话语:一般用于子类调用父类的方法
例如,有如下三个类

from inspect import getmro


class A(object):
    def __init__(self) -> None:
        pass

    def run2(self):
        print("run2 A")

    def run(self):
        print("run A")


class B(A):
    def __init__(self) -> None:
        super().__init__()

    def run2(self):
        print("run B")


class C(A):
    def __init__(self) -> None:
        super().__init__()

    def run2(self):
        print("run C")

当D调用B中的方法时

class D(B, C):
    def __init__(self) -> None:
        super().__init__()

    def run2(self):
        super(B, self).run2()


print(D.__mro__)
print(getmro(D))
D().run2()
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
run C

发现未显示B,显示的是C,由此可见super查找的时候,是从type后面去找,不算type

super使用场景

1.子类调用父类的方法,子类初始化父类的构造方法

class B(object):
    def __init__(self, name) -> None:
        self.name = name

    def run(self):
        print("B run")


class C(B):
    def __init__(self, name, age) -> None:
        super(C, self).__init__(name)
        self.age = age

    def run(self):
        super().run()
        print(f"name:{self.name}, age:{self.age}")


C("tom", 18).run()
B run
name:tom, age:18

2.出现在覆盖Python特殊方法的代码

class Proxy:
    def __init__(self, obj):
        self._obj = obj

    # Delegate attribute lookup to internal obj
    def __getattr__(self, name):
        return getattr(self._obj, name)

    # Delegate attribute assignment
    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name, value)  # Call original __setattr__
        else:
            setattr(self._obj, name, value)

在上面代码中,__setattr__() 的实现包含一个名字检查。 如果某个属性名以下划线(_)开头,就通过 super() 调用原始的 __setattr__(), 否则的话就委派给内部的代理对象 self._obj 去处理。 这看上去有点意思,因为就算没有显式的指明某个类的父类, super() 仍然可以有效的工作。

Python继承的原理

例如存在如下代码

class A(object):
    def __init__(self) -> None:
        pass

    def run(self):
        print("A run")


class B(object):
    def __init__(self) -> None:
        pass

    def run(self):
        print("B run")


class C(A, B):
    def __init__(self) -> None:
        super().__init__()

    def run(self):
        super(C, self).run()


C().run()
print(C.__mro__)
A run
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

在python继承过程中,会基于MRO列表从当前类的位置从左到右依次遍历检查类,直到找到符合要求的类,一版在查找过程中遵循以下准则:

  • 子类会先于父类被检查
  • 多个父类会根据它们在列表中的顺序被检查, 就是从左向右,谁在前,谁先被检查
  • 如果对下一个类存在两个合法的选择,选择第一个父类, 同第二点,在一个类中找到了第一个属性则不会继续查找
    除此之外,super()不仅仅可以使用在子类查找父类当中,也可以使用在非父类中,需要结合继承一起使用,super则就是基于继承的MRO列表进行顺序查找,如下:
class A(object):
    def __init__(self) -> None:
        pass

    def run(self):
        print("A run")
        super().run()


A().run()
A run
Traceback (most recent call last):
  File "c:\Users\ts\Desktop\2022.7\2022.8.5\test.py", line 88, in <module>
    A().run()
  File "c:\Users\ts\Desktop\2022.7\2022.8.5\test.py", line 85, in run
    super().run()
AttributeError: 'super' object has no attribute 'run'

由结果可知,当一个单独的类调用super时会直接报错,但使用如下代码进行调用时则不会报错

class A(object):
    def __init__(self) -> None:
        pass

    def run(self):
        print("A run")
        super().run()


class B(object):
    def __init__(self) -> None:
        pass

    def run(self):
        print("B run")


class C(A, B):
    def __init__(self) -> None:
        super().__init__()


c = C()
c.run()
print(C.__mro__)
A run
B run
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

C在调用A的run方法时, 其中A中的super()方法调用的是B中的方法,这是为什么呢,其实从C的mro列表中可以看出,B在A的后面,super不管是在子类还是父类中都遵循mro列表中的顺序依次查找属性或者方法,直到找到所需的。

总结

  • super()不仅可以出现在子类使用父类的方法中,也可以出现在父类当中
  • super()的调用过程遵循当前调用类的MRO列表,依据当前调用类的位置,从左向右依次遍历检查,直到找到所需的内容

补充

mro列表的获取

C.__mro__

from inspect import getmro
getmro(C)

参考

super官方文档
python3-cookbook

posted @ 2022-08-05 14:12  形同陌路love  阅读(35)  评论(0编辑  收藏  举报