super()

作者: import_hello | 来源:发表于2018-09-05 19:10 被阅读0次

转载须注明出处:简书@Orca_J35
可参考另外两则笔记:调用父类中的方法 | 方法解析顺序(MRO)

目录.jpg

super([type1[, object-or-type2]])

该内置函数本质上来说是在调用 super 类的构造函数,从而生成一个 super 实例。通过 super 实例可调用 "游标类(type1)" 的父类或兄弟类[^1]中的方法。super 类的构造函数大致如下:(这里只为了是方便理解,真正的实现过程并非如此)

class super:
    def __init__(self, type1, objc_or_type2=None):
        """
        :param type1:游标类,用于标记MRO列表搜索的起点
        :param objc_or_type2:被绑定实例或类
        """
        if objc_or_type2 is not None:
            """构造一个已绑定的super实例"""
            if isinstance(objc_or_type2, type1):
                # 游标类
                self._cursor = type1
                # 被绑定的实例对象 objc_or_type2
                self._instance = objc_or_type2
                # 被绑定的类对象 objc_or_type2.__class__
                self._cls = objc_or_type2.__class__
                # 获取MRO列表 
                self._mro = self._cls.__mro__
            elif issubclass(objc_or_type2, type1):
                # 游标类
                self._cursor = type1
                # 被绑定的类对象,由于缺少实例,所以不支持实例方法调用
                self._cls = objc_or_type2
                # 获取MRO列表
                self._mro = objc_or_type2.__mro__
            else:
                raise TypeError("super(type, obj): \
                    obj must be an instance or subtype of type")
        else:
            """构造一个未绑定的super实例"""
            pass

这里需要特别注意"游标类"、"被绑定的实例对象"(self._instance)、"被绑定的类对象"(self._cls),下面会详细阐述 super 对象这三个参数是如何协作的。

1. super(type1, obj)

objc_or_type2 是一个实例对象时:

# 双参数形式可用于任何地方,不限于类方法。
super(type1, obj) -> 
bound super object; requires isinstance(obj, type1) == True
# 无参数形式仅用在类方法中,因为编译器会根据其所在的类来填写剩余的两个参数。
super() -> same as super(self.__class__, self)

下面给出一个简单的类层次结构,并且在类外部展示通过 super 实例调用各种方法的过程。

class A:
    class_field = "A类的类字段"

    def __init__(self):
        print("A -> __init__")
        self._field = "_instance_A"

    def instance_method(self):
        print(self._field)

    @classmethod
    def class_method(cls):
        print("类名:", cls.__name__)

    @staticmethod
    def static_method():
        print("A_staticmethod")

class X:pass

class B(X,A):
    class_field = "B类的类字段"

    def __init__(self):
        print("B -> __init__")
        self._field = "_instance_B"

构造 super 实例 super_objc

>>> b_objc = B() # 构造 B 类实例,初始化实例字段
B -> __init__
>>> super_objc = super(B,b_objc) 
# super_objc实例的"被绑定的实例对象":super_objc._instance=b_objc
# super_objc实例的"被绑定的类对象":super_objc._cls=B

通过 super_objc 调用实例方法时,super_objc 会在 MRO 列表中依次查找位于游标类之后的各个类,谁第一个拥有该实例方法,便会将该类的此实例方法绑定到b_objc 中;如果没有类拥有此方法,则抛出 AttributeError

比如,在调用 super_objc.instance_method() 后,类 A 是 MRO 列表中游标类之后第一个拥有该方法的类(类 X 虽然在类 A 之前,但没有该实例方法)。便会将A.instance_method 绑定到 b_objc 中(假设绑定后会在 b_objc 中创建一个名为 A_inst_methd 的实例方法)。然后再会调用 b_objc.A_inst_methd() , 此时 A_inst_methd()b_objc 实例作为第一参数使用,也就是说 A_inst_methd() 会套用 b_objc 的实例属性。完成调用后会删除该绑定方法。

>>> super_objc.instance_method()
_instance_B # 实例方法使用b_objc作为第一参数,因此会输出b_objc的实例字段
>>> super_objc.instance_method
<bound method A.instance_method of <__main__.B object at 0x0000029269780AC8>>

通过 super_objc 调用类方法时,super_objc 会在 MRO 列表中依次查找位于游标类之后的各个类,谁第一个拥有此类方法,便会将该类的此类方法绑定到b_objc 中;如果没有类拥有此方法,则抛出 AttributeError

比如,在调用 super_objc.class_method() 后,类 A 是 MRO 列表中游标类之后第一个拥有该方法的类(类 X 虽然在类 A 之前,但没有该类方法)。便会将A.class_method 绑定到 B 类中(假设绑定后会在 B 类中创建一个名为 A_clas_methd 的类方法)。然后再会调用 B.A_clas_methd() , 此时 A_clas_methd() 中被省略的第一参数 cls 指向 B 类,也就是说 A_clas_methd() 将 B 类作为第一参数使用。完成调用后会删除该绑定方法。

>>> super_objc.class_method()
类名: B # 实例方法使用B类作为第一参数,因此会输出B类的类字段
>>> super_objc.class_method
<bound method A.class_method of <class '__main__.B'>>

通过 super_objc 调用静态方法时,super_objc 会在 MRO 列表中依次查找位于游标类之后的各个类,谁第一个拥有此静态方法,便会直接调用该类的此静态方法,不会进行绑定;如果没有类拥有此方法,则抛出 AttributeError

>>> super_objc.static_method()
A_staticmethod
>>> super_objc.static_method
<function A.static_method at 0x00000292697CFEA0>

通过 super_objc 调用类字段时,super_objc 会在 MRO 列表中依次查找位于游标类之后的各个类,谁第一个拥有此类字段,便会直接返回该类的该类字段,不会进行绑定;如果没有类拥有此方法,则抛出 AttributeError

>>> super_objc.class_field
'A类的类字段'

2. super(type1, type2)

objc_or_type2 是一个类时:

# 双参数形式可用于任何地方,不限于类方法。
super(type1, type2) -> 
bound super object(this is useful for classmethod and staticmethod); requires issubclass(type2, type1) == True

下面给出一个简单的类层次结构,并且在类外部展示通过 super 实例调用各种方法的过程。由于只有"被绑定的类对象"(self._cls),所以 super 对象无法调用实例方法,因此这里省略掉类实例方法。

"""类层次结构"""
class A():
    class_field = "A类的类字段"
    @classmethod
    def class_method(cls):
        print("类名:", cls.__name__)

    @staticmethod
    def static_method():
        print("A_staticmethod")

class X:pass

class B(X,A):
    class_field = "B类的类字段"

构造 super 实例 super_objc

>>> super_objc = super(B,B)
# super_objc实例的"绑定实例对象":没有
# super_objc实例的"绑定类对象":super_objc._cls=B

通过 super_objc 调用类方法时,super_objc 会在 MRO 列表中依次查找位于游标类之后的各个类,谁第一个拥有此类方法,便会将该类的此类方法绑定到 B 类中;如果没有类拥有此方法,则抛出 AttributeError

比如,在调用 super_objc.class_method() 后,类 A 是 MRO 列表中游标类之后第一个拥有该方法的类(类 X 虽然在类 A 之前,但没有该类方法)。便会将A.class_method 绑定到 B 类中(假设绑定后会在 B 类中创建一个名为 A_clas_methd 的类方法)。然后再会调用 B.A_clas_methd() , 此时 A_clas_methd() 将类 B 作为第一参数使用,也就是说 A_inst_methd() 会套用 B 的类属性。完成调用后会删除该绑定方法。

>>> super_objc.class_method()
类名: B # 实例方法使用B类作为第一参数,因此会输出B类的类字段
>>> super_objc.class_method
<bound method A.class_method of <class '__main__.B'>>

通过 super_objc 调用静态方法时,super_objc 会在 MRO 列表中依次查找位于游标类之后的各个类,谁第一个拥有此静态方法,便会直接调用该类的此静态方法,不会进行绑定;如果没有类拥有此方法,则抛出 AttributeError

>>> super_objc.static_method()
A_staticmethod
>>> super_objc.static_method
<function A.static_method at 0x00000292697CFEA0>

通过 super_objc 调用类字段时,super_objc 会在 MRO 列表中依次查找位于游标类之后的各个类,谁第一个拥有此类字段,便会直接返回该类的该类字段,不会进行绑定;如果没有类拥有此方法,则抛出 AttributeError

>>> super_objc.class_field
'A类的类字段'

3. super(type1)

objc_or_type2 为空时:

super(type1) -> unbound super object

第二参数的默认值为是 None

4. 只用第一个

super 对象会在 MRO 列表中依次查找游标类之后的各个类,谁第一个拥有目标方法,便会将该类的此方法绑定到 self._instance 或 self._cls 中(注:静态方法直接调用,不会绑定),然后通过 self._instance 或 self._cls 调用被绑定的方法,剩余的类都会被忽略;如果游标类之后的所有类都不包含目标方法,则抛出 AttributeError。

class A():
    def func(self):
        print ("enter A")

class B():
    def func(self):
        print ("enter B")

class C(A, B):
    def func(self):
        print ("enter C")
        super().func()

只有 A 类中的 func 方法会被绑定到 C 类实例中,并且会通过 C 类实例调用该绑定方法。尽管 B 类也拥有 func 方法,但不会被用到。

>>> C().func()
enter C
enter A

如果需要同时调用 B 类中的方法,则需要将继承关系修改为:

class A():
    def func(self):
        print ("enter A")
        super().func()

class B():
    def func(self):
        print ("enter B")

class C(A, B):
    def func(self):
        print ("enter C")
        super().func()

验证:

>>> C().func()
enter C
enter A
enter B

4. 指定游标类

下面的代码展示了一个典型的"钻石型"多继承的类层次结构。

class Base(object):
    def func(self):
        print ("enter Base")

class A(Base):
    def func(self):
        print ("enter A")

class B(Base):
    def func(self):
        print ("enter B")

class C(A, B):
    def func(self):
        print ("enter C")

继承关系链如下:

      Base
      /  \
     /    \
    A      B
     \    /
      \  /
       C

C 类的 MRO 列表如下:

[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>]

通过改变游标类(type1) ,可改变 MRO 列表的搜索起点。下面这个示例中,我们将游标类改为 B 类,则会使用 Base 中的 func 方法。

>>> super(B,C()).func()
enter Base

6. 其他提醒

注意,如果 MRO 中游标类之后的类中包含 __getitem__ 方法,那么 super 对象支持显式调用 super().__getitem__(name),但并不支持隐式调用(如 super()[name])。因为,super().__getattribute__() 会拦截 __getitem__ ,并在其内部处理 __getitem__。但 super 对象并没有独立实现 __getitem__ 方法,所以并不支持 super()[name]

class A():
    def func(self):
        print ("enter A")
        super().func()
    def __getitem__(self,name):
        print(name)


class C(A):
    def func(self):
        print ("enter C")
        super().func()

测试:

>>> A()[2]
2
>>> super(C,C()).__getitem__(2)
2
>>> super(C,C())[2]
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    super(C,C())[2]
TypeError: 'super' object is not subscriptable
>>> 

通过 help 函数,我们可以查看 super 的文档信息,以了解其包含的方法。

>>> help(super)
Help on class super in module builtins:

class super(object)
 |  --snip--
 |  Methods defined here:
 |  
 |  __get__(self, instance, owner, /)
 |      Return an attribute of instance, which is of type owner.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  --snip--

参考

注脚:
1: What is a sibling class in Python?

相关文章

  • Super, super, super, super happy

    Parent diaries for 137th days weather:sunny Wed...

  • Super, super, super, super fun d

    亲子日记第136天 天气:晴 星期二 今天是周二,超级超级超级超级超级超级开心的一天٩(๑^o...

  • Class中的super简析

    super当作函数使用 super()执行父类的构造函数 super() 返回的是子类的实例,即 super 内部...

  • reactES6写法

    注意: super()是为了使用this,必须在使用this之前声明super(); super(props)这个...

  • JAVA面试题

    Q:super()与 this()的区别? A:This():当前类的对象,super 父类对象。 Super()...

  • iOS - super | super | super clas

    super 是编译器的指示符,不是指针,只是一个标识符,代表调用父类的方法,调用者还是自己本身 superclas...

  • super

    super关键字的使用 super理解为:父类的 super可以用来调用:属性、方法、构造器 super的使用:(...

  • Java 泛型 <? super T> 中 supe

    Java 泛型 中 super 怎么 理解?与 extends 有何不同? super只能...

  • Wildcards with super

    What is Wildcards with super The quizzical phrase ? super...

  • Java中Super()与this()

    Java中Super()与this() super指父类,this指当前对象super()与this()都必须在构...

网友评论

    本文标题:super()

    本文链接:https://www.haomeiwen.com/subject/cwygbftx.html