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

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--
网友评论