深度解析并实现python中的super

深度解析并实现python中的super

    • 概述
    • super的定义
    • 函数bound和描述器
    • super的典型用法
    • super的本质
    • 自定义super
    • python中对super的实现
    • 写在最后

概述

python中的super是一个神奇的存在。本文对python中的super进行深入的讲解,首先说明super的定义,并列举一下super的典型用法,然后会对和super相关的语言特性进行讲解,比如mro(方法解析顺序),descriptor描述器,函数绑定,最后尝试自己动手实现一个super,并简单探索一下python中对super的实现。

super的定义

首先看一下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)

从文档里可以看出以下几点:

1 super是一个类

super不是关键字,而是一个类, 调用super()会创建一个super对象:

>>> class A:
...     def __init__(self):
...         su = super()
...         print(su)
...         print(type(su))
...
>>> a = A()
<super: <class 'A'>, <A object>>
<class 'super'>

或者:

>>> class A:
...     pass
...
>>> a = A()
>>> su = super(A, a)
>>> su
<super: <class 'A'>, <A object>>
>>> type(su)
<class 'super'>
>>>

2 super支持四种调用方式

  1. super()
  2. super(type, obj)
  3. super(type)
  4. super(type, type1)

其中super(type)创建一个未绑定super对象(unbound),其余三种方式创建的是绑定的super对象(bound)。super()是python3中支持的写法,是一种调用上的优化,其实相当于第一个参数传入调用super的当前的类,第二个参数传入调用super的方法的第一个参数。

关于super的定义先介绍到这里,下面介绍bound相关的概念,bound的概念又和描述器相关,所以接下来介绍函数bound和描述器

函数bound和描述器

要理解bound,首先要理解在python中,函数都是对象,并且是描述器。

函数都是对象:

>>> def test():
...     pass
...
>>> test
<function test at 0x10a989268>
>>> type(test)
<class 'function'>
>>>

test是一个函数,同时又是一个function对象。所以当我们使用def定义一个函数的时候,相当于创建一个function对象。因为function实现了__call__方法,所以可以被调用:

>>> getattr(test, '__call__')
<method-wrapper '__call__' of function object at 0x10a989268>
>>>

由于function实现了__get__方法,所以,函数对象又是一个描述器对象(descriptor):

>>> getattr(test, '__get__')
<method-wrapper '__get__' of function object at 0x10a989268>

因为根据python的定义,只要实现了__get__, __set__和__delete__中的一个或多个,就认为是一个描述器。

描述器的概念和bound的概念,在模块函数上提现不出来,但是如果一个函数定义在类中,这两个概念会体现的很明显。

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

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

首先验证在类中定义的函数也是一个function对象:

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

下面验证在类中定义的函数也是一个描述器,也就是验证实现了__get__方法:

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

从上面的验证可以看到,在类中定义的函数,也是一个描述器对象。所以可以认为在类中定义函数,相当于定义一个描述器。所以当我们写下面代码时:

class A:
    def test(self):
        pass

相当于这样:

class A:
    test = function()

下面简单讲一下描述器的特性。看下面的代码:

class NameDesc:
    def __get__(self, instance, cls):
        print('NameDesc.__get__:', self, instance, cls)
        if instance is None: #通过类访问描述器的时候,instance为None
            return self
        else:
            return instance.__dict__['_name']

    def __set__(self, instance, value):
        print('NameDesc.__set__:', self, instance, value)
        if not isinstance(value, str):
            raise TypeError('expect str')
        instance.__dict__['_name'] = value

class Person:
    name = NameDesc()

p = Person()

p.name = 'zhang'
print(p.name)
print(Person.name)

输出结果为:

NameDesc.__set__: <__main__.NameDesc object at 0x10babaf60> <__main__.Person object at 0x10babaf98> zhang
NameDesc.__get__: <__main__.NameDesc object at 0x10babaf60> <__main__.Person object at 0x10babaf98> 
zhang
NameDesc.__get__: <__main__.NameDesc object at 0x10e8dbf98> None 
<__main__.NameDesc object at 0x10e8dbf98>

当一个类(Person)中存在一个描述器属性(name), 当这个属性被访问时,会自动调用描述器的__get__和__set__方法:

  1. 当使用类名访问描述器时(Person.name) , __get__方法返回描述器本身
  2. 当使用对象访问描述器时(p.name), __get__方法会返回自定义的值(instance._name),我们可以自定义返回任何值,包括函数

回到上面的两段等效代码:

class A:
    def test(self):
        pass
class A:
    test = function()

那么既然test是一个描述器,那么我通过A调用test和通过a调用test时,会返回什么呢?下面直接看结果:

>>> class A:
...     def test(self):
...         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>>

通过类A访问test(A.test),还是会返回test这个描述器自身,也就是A.__dict__['test']
通过对象a访问test(a.test), 返回一个bound method。

所以我们可以认为:

  1. function的__get__方法,当不传入instance时(相当于A.test),会返回function本身
  2. 当传入一个instance的时候(相当于a.test),会返回一个bound method。

下面的代码可以验证这个结论:

>>> A.test.__get__(None, A)
<function A.test at 0x1088db158>
>>> A.test.__get__(None, A) == A.test
True
>>>
>>> A.test.__get__(a, A)
<bound method A.test of <__main__.A object at 0x1088d9860>>
>>> A.test.__get__(a, A) == a.test
True

所以我们可以认为描述器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()

下面看一下绑定(bound)和非绑定(unbound)到底有什么区别。 接着看下面的示例:

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

我们看到,在定义A的时候,test方法是有一个参数self的。
A.test返回一个function对象,是一个未绑定函数,所以调用的时候要传对象(A.test(a))
a.test返回一个bound method对象,是一个绑定函数,所以调用的时候不需要再传入对象(a.test())

可以看出,所谓绑定,就是把调用函数的对象,绑定到函数的第一个参数上。

做一个总结,本节主要讲解了函数,描述器和绑定的概念。结论就是function是一个可以被调用(实现了__call__方法)的描述器(实现了__get__方法)对象,并且通过类获取函数对象的时候,__get__方法会返回function本身,通过实例获取函数对象的时候,__get__方法会返回一个bound method,也就是将实例绑定到这个function上。

下面再回到super。

super的典型用法

很多人对super直观的理解是,调用父类中的方法:

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

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

b = B()
b.test()

执行结果为:

A.test
B.test

从上面的例子看来,super确实可以调用父类中的方法。但是看下面的代码:

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

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

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


b = B()
b.test()

打印结果:

B.test
TestMixin.test
A.test

上面的代码先创建B的对象b,然后调用b.test(),但是B的test函数通过super(),会调到第一个父类TestMixin的test函数,因为TestMixin是B的第一个父类。

TestMixin中的test函数中通过super调到了A中的test函数,但是A不是TestMixin的父类。在这个继承体系中,A和TestMixin都是B的父类,但是A和TestMixin没有任何继承关系。为什么TestMixin中的super会调到A中的test函数呢?

super的本质

其实super不是针对调用父类而设计的,它的本质是在一个由多个类组成的有序集合中搜寻一个特定的类,并找到这个类中的特定函数,将一个实例绑定到这个函数上,生成一个绑定方法(bound method),并返回这个bound method。

上面提到的由多个类组成的有序集合,即是类的mro,即方法解析顺序(method resolution ),它是为了确定在继承体系中,搜索要调用的函数的顺序的。通过inspect.getmro或者类中的__mro__属性可以获得这个集合。还是以上面的A, TestMixin,B为例:

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

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

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


#b = B()
#b.test()

print(B.__mro__)

输出结果为:

(, , , )

可见B的mro为(B, TestMixin, A, object)。这个列表的意义是B的实例b在调用一个函数时,首先在B类中找这个函数,如果B中调用了super,则需要从B的下一个类(即TestMixin)中找函数,如果在TestMixin中又调用了super,则从TestMixin的下一个类(即A)中找函数。

在python 2.x中,要成功调用super必须指定两个参数才行,即super(type,obj)或super(type, type1)。为了直观, 我们用这种带参数的形式改写上面的示例:

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

class TestMixin:
    def test(self):
        print('TestMixin.test')
        super(TestMixin, self).test()

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


print(B.__mro__)

b = B()
b.test()

其实这两个参数很关键,第一个参数是当前调用super的类,这个参数就是为了在mro中找到下一个类,然后从这个类开始搜寻函数。第二个参数有两个作用,一是确定从哪个类获取mro列表,二是作为实例,绑定到要调用的函数上。

我们以TestMixin的super(TestMixin, self).test()为例,解释这两个参数的意义。

先看第二个参数,需要知道, 当从b.test()一层层的向上调时,self始终是实例b,所以不管调到哪个类中的super,self始终是b,通过这个self获取的mro永远都是B的mro。当获取到mro后,就在mro中找第一个参数TestMixin的下一个类,这里是A, 并且在A里面查找有没有目标函数,如果没有,就在A类的下一个类中找,依次类推。

还有,通过super(TestMixin, self)创建的是super对象,super并没有test方法,那么super(TestMixin)为什么能调用test方法呢?

这是因为当一个对象调用类中没有的方法时,会调用类的__getattr__方法,在super中只要实现这个方法,就会拦截到super(TestMixin, self)对test的访问,根据上面的介绍,super中可以根据传入的TestMixin和self,确认了要在A中查找方法,所以这里我们可以直接从A查找test函数,如果A中没有,那么就从mro中A后面的类依次查找。

等找到这个函数后,不能直接返回这个test函数,因为这个函数还没有绑定,需要通过这个函数(也是描述器)的__get__函数,将self实例传入,获得一个绑定方法(bound method),然后将这个bound method返回。所以到此为止,super(TestMixin, self).test 就获取了一个bound method, 这个是A中的函数,并且绑定了self实例(这个实例是b)。然后在后面加一个(), super(TestMixin, self).test()的意义就是调用这个bound method。所以就调到了A中的test函数:

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

因为绑定的是实例b, 所以上面test中传入的self就是实例b。

到此为止,super的原理就讲完了。

自定义super

上面讲解了super的本质,根据上面的讲解,我们自己来实现一个my_super:

class my_super:
    def __init__(self, thisclass=None, target=None):
        self._thisclass = thisclass
        self._target = target


    def _get_mro(self):
        if issubclass(type, type(self._target)):
            return self._target.__mro__ #第二个参数是类型
        else:
            return self._target.__class__.__mro__ #第二个参数是实例


    def _get_function(self, name):
        mro = self._get_mro()
        if not self._thisclass in mro:
            return None

        index = mro.index(self._thisclass) + 1
        while index < len(mro):
            cls = mro[index]
            if hasattr(cls, name):
                attr = cls.__dict__[name]
                #不要用getattr,因为我们这里需要获取未绑定的函数
                #如果使用getattr, 并且获取的是classmethod
                #会直接将cls绑定到该函数上
                #attr = getattr(cls, name)
                if callable(attr) or isinstance(attr, classmethod):
                    return attr
            index += 1
        return None

    def __getattr__(self, name):
        func = self._get_function(name)
        if not func is None:
            if issubclass(type, type(self._target)):
                return func.__get__(None, self._target)
            else:
                return func.__get__(self._target, None)

和super一样,上面的my_super的__init__函数接收两个参数,一个是调用super的当前类thisclass, 第二个参数target是调用my_super的函数的第一个参数,也就是self或cls。所以这个参数可能是对象实例,也可能是类(如果在classmethod中调用my_super,第二个参数要传cls),在my_super中要分两种情况。

my_super中的_get_mro函数,根据传入的第二个参数获取mro。如果第二个参数target是对象实例,就获取它的__class__,然后获取__class__的__mro__,如果target是类,则直接获取target的__mro__。

my_super的_get_function函数,先获取mro,然后在mro上获取位于thisclass后的目标类,并且在目标类中查找函数,参数name是要查找的函数的名字。这里要注意,如果位于thisclass后的类中没有名为name的函数,则继续在下各类中查找,所以使用了while循环

my_super的__getattr__函数,用于截获my_super对象对方法的调用,举例来说,如果my_supe调用的是test,那么这个name就是’test’。在__getattr__中,首先调用_get_function,获取目标函数,然后调用函数的描述器方法__get__,将target实例绑定,然后将绑定后的方法返回。这里也发要分target是实例还是类。如果是实例(这时调用my_super的是实例函数),则使用function.__get__(instance, None)绑定,如果是类(这是调用my_super的是类函数),则使用functon.__get__(None, cls)绑定。

我们改写上面的例子,来验证my_super功能是否正常:

from my_super import my_super

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

class TestMixin:
    def test(self):
        print('TestMixin.test')
        my_super(TestMixin, self).test()

class B(TestMixin, A):
    def test(self):
        print('B.test')
        my_super(B, self).test()


print(B.__mro__)

b = B()
b.test()

执行后输出如下:

B.test
TestMixin.test
A.test

和super的效果是一样的。

下面我们在写一个菱形继承的实例来验证,并且验证类函数中使用my_super功能是否正常:

from my_super import my_super

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

    @classmethod
    def test1(cls):
        print('A.test1')

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

    @classmethod
    def test1(cls):
        print('B.test1')
        my_super(B, cls).test1()

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

    @classmethod
    def test1(cls):
        print('C.test1')
        my_super(C, cls).test1()

class D(B,C):
    def test(self):
        print('D.test')
        my_super(D, self).test()

    @classmethod
    def test1(cls):
        print('D.test1')
        my_super(D, cls).test1()

d = D()
d.test()

D.test1()

输出如下:

D.test
B.test
C.test
A.test
D.test1
B.test1
C.test1
A.test1

输出结果正常,可见我们自定义实现的my_super即支持在实例函数中调用,也可以在类函数中调用。

最后有一点不足,就是my_super必须传入参数,而super在python3中可以不用传参数,应该是在底层自动捕获了调用super的类和调用super的函数的第一个参数。

通过inspect.stack(), inspect.signature(), sys._getframe()等api应该可以获取调用my_super的函数的第一个参数,但是调用my_super的类不知道如何获取。如果哪位有解决方案,可以留言。

python中对super的实现

python中的super是在c中实现的,在最新的python 3.7.0源码中,super实现在Python-3.7.0/Objects/typeobject.c中,和python层中的super对应的,是c层中的superobject:

 typedef struct {
      PyObject_HEAD
      PyTypeObject *type;
      PyObject *obj;
      PyTypeObject *obj_type;
  } superobject;

其中在super_getattro函数中有以下代码:

do {
          PyObject *res, *tmp, *dict;
          descrgetfunc f;

          tmp = PyTuple_GET_ITEM(mro, i);
          assert(PyType_Check(tmp));

          dict = ((PyTypeObject *)tmp)->tp_dict;
          assert(dict != NULL && PyDict_Check(dict));

          res = PyDict_GetItem(dict, name);
          if (res != NULL) {
              Py_INCREF(res);

              f = Py_TYPE(res)->tp_descr_get;
              if (f != NULL) {
                  tmp = f(res,
                      /* Only pass 'obj' param if this is instance-mode super
                         (See SF ID #743627)  */
                      (su->obj == (PyObject *)starttype) ? NULL : su->obj,
                      (PyObject *)starttype);
                  Py_DECREF(res);
                  res = tmp;
              }

              Py_DECREF(mro);
              return res;
          }

          i++;
      } while (i < n);

可以看出确实是在类的mro列表中查找类的。
tmp = PyTuple_GET_ITEM(mro, i)现在mro中查找一个类,然后dict = ((PyTypeObject *)tmp)->tp_dict获取这类的__dict__字典,res = PyDict_GetItem(dict, name)在字典中查找函数

super_init函数对应python层super的__init__函数:

static int
  super_init(PyObject *self, PyObject *args, PyObject *kwds)
  {
      superobject *su = (superobject *)self;
      PyTypeObject *type = NULL;
      PyObject *obj = NULL;
      PyTypeObject *obj_type = NULL;

      if (!_PyArg_NoKeywords("super", kwds))
          return -1;
      if (!PyArg_ParseTuple(args, "|O!O:super", &PyType_Type, &type, &obj))
          return -1;

      if (type == NULL) {
          /* Call super(), without args -- fill in from __class__
             and first local variable on the stack. */
          PyFrameObject *f;
          PyCodeObject *co;
          Py_ssize_t i, n;
          f = PyThreadState_GET()->frame;
          if (f == NULL) {
              PyErr_SetString(PyExc_RuntimeError,
                              "super(): no current frame");
              return -1;
          }
          co = f->f_code;
          if (co == NULL) {
              PyErr_SetString(PyExc_RuntimeError,
                              "super(): no code object");
              return -1;
          }
    ......
    ......

上面的代码中type == NULL的if分支,就是对应在python中不传参数调用super()的情况,可以看到,在c中也是通过回退调用栈(PyFrameObject)来获取调用super的类和调用super的函数的第一个参数的。

写在最后

本文实现my_super只是根据自己对super的理解,python中真实的super的一些实现细节可能并没有考虑到。并且本人对my_super并没做充分的测试,不能保证在任何场景下都能工作正常。

本人是刚学了半个月python的新手,本文中如有错误的地方,欢迎留言指正。

你可能感兴趣的:(Python)