类 super 描述器

  • 前言
  • super的定义
  • 描述器
  • super的工作原理
  • 总结
  • 引用


前言

你真的懂super吗?

什么是Super VLAN 什么是super idol的笑容_实例方法

首先,super不是指的小象战队的五号位,也不是IG战队的教练,望周知!

写这篇文章是因为读到python cookbook的8.7和8.8章,挺懵逼也挺丢人的,所以这次就把它弄个明明白白咯。

super的定义

敲下 help(super) ,你会得到:

Help on class super in module builtins:

class super(object)
 |  super() -> same as super(__class__, <first argument>)
 |  super(type) -> unbound super object
 |  super(type, obj) -> bound super object; requires isinstance(obj, type)
 |  super(type, type2) -> bound super object; requires issubclass(type2, type)
 |  Typical use to call a cooperative superclass method:
 |  class C(B):
 |      def meth(self, arg):
 |          super().meth(arg)
 |  This works for class methods too:
 |  class C(B):
 |      @classmethod
 |      def cmeth(cls, arg):
 |          super().cmeth(arg)

可以看到super是个类,有四种调用方式。其中 super(type) 创建一个未绑定的super对象,其余三种方式创建的是绑定的super对象。super() 是python3中支持的写法,是一种调用上的优化,相当于 super(__class__, self) 也就是第三种方式。

那么绑定和非绑定到底是什么意思呢,第三种和第四种的调用方式区别又在哪呢?咱接着看

描述器

首先的首先,你需要了解描述器

下面我们在类中定义一个函数:

>>> class A:
...     def test(self):
...         pass

验证在类中定义的函数是一个function对象,也是一个描述器(因为实现了__get__方法):

>>> A.__dict__['test']
<function A.test at 0x10aab4158>
>>>
>>> type(A.__dict__['test'])
<class 'function'>

>>> getattr(A.__dict__['test'], '__get__')
<method-wrapper '__get__' of function object at 0x10aab4158>

我们知道通过类和类实例调用描述器会得到不同的结果,这里验证下:

>>> class A:
...     def test(self): # 可以看作 test = function('test')
...         pass
...
>>> A.test
<function A.test at 0x1088db0d0>
>>>
>>> A.test is A.__dict__['test']
True
>>>
>>> a = A()
>>> a.test
<bound method A.test of <__main__.A object at 0x1088d9780>>

果然,直接通过类来访问test,会得到描述器本身,也就是function对象;通过实例a来访问test,会得到一个bound method对象。

所以我们可以认为描述器function的实现方式如下:

class function:

    def __get__(self, instance, cls):
        if instance is None: #通过类调用
            return self
        else: #通过对象调用
            return self._translate_to_bound_method(instance)

    def _translate_to_bound_method(self, instance):
        #
        # ...
        #


class A:
    test = function()

那么function和bound method有什么区别呢:

>>> class A:
...     def test(self):
...         print('*** test ***')
...
>>> a = A()
>>>
>>> A.test(a)
*** test ***
>>>
>>> a.test()
*** test ***

可以看到bound method会自动把调用对象(a)绑定给第一个参数self,而function需要手动传入a。

然而这个bound和super定义里的bound是啥关系呢,咱回到super。

super的工作原理

通常我们使用到super仅仅是调用父类中的方法,但当涉及到多继承的时候,情况可能就不一样了:

class A:
    def test(self):
        print('A.test')

class B:
    def test(self):
        print('B.test')
        super().test()

class C(B, A):
    def test(self):
        print('C.test')
        super().test()


c = C()
c.test()

# 输出
C.test
B.test
A.test

这里A类和不是B类的父类,为什么super会调用到A的函数呢。

super(C, self).test()实际上会在第二个参数[object-or-type]self的MRO里,遍历第一个参数[type]C之后的类,直到找到test属性。

MRO即Method Resolution Order,上面类C的MRO可以这样得到:

C.__mro__
(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
C.mro()
[<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]

类C里的super()实际上是super(C, self),这里的self也就是c,所以从C的下一个类也就是B开始找,调用B.test(c )。

类B里的super()实际上是super(B, self),这里的self还是c,所以从B的下一个类也就是A开始找,调用A.test(c )。

有没有发现self和c绑定在一起了,bound!

为了实现这些,super对象储存了第一个参数和第二个参数以及第二个参数的type:

print(c)
# <__main__.C object at 0x0000000002397B00>
su = super(C, c)
print(su.__thisclass__, su.__self__, su.__self_class__)
# <class '__main__.C'> <__main__.C object at 0x0000000002397B00> <class '__main__.C'>

所以寻找属性时,__self_class__的__mro__会被搜寻,从__thisclass__的后一个位置开始,而且__self__会绑定。

回到super的第二种调用方式,只传入一个参数会让__self__和__self_class__设为None,无法查找。

但是super对象是支持描述器协议的,所以你可以这样绑定:

# super对象的__get__方法
def __get__(self, instance, cls=None):
    return super(self.__thisclass__, instance)


su = super(C)
print(su.__thisclass__, su.__self__, su.__self_class__)
# <class '__main__.C'> None None
su = su.__get__(c, C)
print(su.__thisclass__, su.__self__, su.__self_class__)
# <class '__main__.C'> <__main__.C object at 0x0000000002597B00> <class '__main__.C'>

这同时意味着我们可以把super对象作为描述器那样来使用,直接绑定到类属性上:

class C(B, A):
    pass

C.su = super(C)
c = C()
c.su.test()

# 输出
B.test
A.test

最后一个问题,super(type, obj)和super(type, type2)有啥不同,很简单,试试就知道了:

c = C()
print(c)
# <__main__.C object at 0x00000000022A7AC8>
su = super(C, c)
print(su, su.__thisclass__, su.__self__, su.__self_class__)
# <super: <class 'C'>, <C object>> <class '__main__.C'> <__main__.C object at 0x00000000022A7AC8> <class '__main__.C'>
su = super(C, C)
print(su, su.__thisclass__, su.__self__, su.__self_class__)
# <super: <class 'C'>, <C object>> <class '__main__.C'> <class '__main__.C'> <class '__main__.C'>

可以看到super(type, type2)返回的super对象是没有实例属性的,所以并不能调用实例方法,你可能又要问了,那这种写法有什么用呢,什么情况下都用super(type, obj)不是万能吗?

哈哈,这就又回到了最初的起点,cookbook里8.8章里面在继承property(也是描述器实现的)时用到了,python文档里有这样一句话:

除了方法查找之外,super() 也可用于属性查找。 一个可能的此种用例是在上级或同级类中调用 descriptor。

总结

  1. 绑定与非绑定,一个是在于super对象的__self__和__self_class__属性,如果都是None的话就不能查找了;一个是在于调用实例对实例方法里参数self的绑定,会一层层传递。
  2. 第二个参数传入实例还是类,对property的继承需要传入类,其他的一致传入实例。
  3. super()找到的不一定是父类,有可能是兄弟类。
  4. 函数也是描述器。