zoukankan      html  css  js  c++  java
  • 深度解析并实现python中的super(转载,好文)

    大神半个月的成绩,让我看的叹为观止,建议看原帖地址,会让你对Python的描述符有更强的认识。

    原文链接:https://blog.csdn.net/zhangjg_blog/article/details/83033210

    深度解析并实现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
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

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

    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'>

        1
        2
        3
        4
        5
        6
        7
        8
        9

    或者:

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

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

    2 super支持四种调用方式

        super()
        super(type, obj)
        super(type)
        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'>
    >>>

        1
        2
        3
        4
        5
        6
        7
        8

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

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

        1
        2
        3

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

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

        1
        2

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

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

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

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

        1
        2
        3
        4

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

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

        1
        2
        3
        4
        5
        6
        7

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

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

        1
        2
        3

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

    class A:
        def test(self):
            pass

        1
        2
        3

    相当于这样:

    class A:
        test = function()

        1
        2

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

    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)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22

    输出结果为:

    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> <class '__main__.Person'>
    zhang
    NameDesc.__get__: <__main__.NameDesc object at 0x10e8dbf98> None <class '__main__.Person'>
    <__main__.NameDesc object at 0x10e8dbf98>

        1
        2
        3
        4
        5

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

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

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

    class A:
        def test(self):
            pass

        1
        2
        3

    class A:
        test = function()

        1
        2

    那么既然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>>

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13

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

    所以我们可以认为:

        function的__get__方法,当不传入instance时(相当于A.test),会返回function本身
        当传入一个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

        1
        2
        3
        4
        5
        6
        7
        8
        9

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

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

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

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

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12

    我们看到,在定义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()

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

    执行结果为:

    A.test
    B.test

        1
        2

    从上面的例子看来,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()

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17

    打印结果:

    B.test
    TestMixin.test
    A.test

        1
        2
        3

    上面的代码先创建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__)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

    输出结果为:

    (<class '__main__.B'>, <class '__main__.TestMixin'>, <class '__main__.A'>, <class 'object'>)

        1

    可见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()

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19

    其实这两个参数很关键,第一个参数是当前调用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')

        1
        2
        3

    因为绑定的是实例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)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40

    和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()

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21

    执行后输出如下:

    B.test
    TestMixin.test
    A.test

        1
        2
        3

    和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()

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45

    输出如下:

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

        1
        2
        3
        4
        5
        6
        7
        8

    输出结果正常,可见我们自定义实现的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;

        1
        2
        3
        4
        5
        6

    其中在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);

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31

    可以看出确实是在类的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;
              }
        ......
        ......

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33

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

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

    本人是刚学了半个月python的新手,本文中如有错误的地方,欢迎留言指正。
    ————————————————
    版权声明:本文为CSDN博主「昨夜星辰_zhangjg」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/zhangjg_blog/article/details/83033210

  • 相关阅读:
    高级前端工程师面试必备(持续更新中)
    用node编写cli工具
    用vscode开发vue应用
    jsbridge的js封装
    react-create-app 构建react项目的流程以及需要注意的地方
    【面试篇】寒冬求职季之你必须要懂的原生JS(中)
    基于vue-cli3.0构建功能完善的移动端架子,主要功能包括
    @vue/cl构建得项目下,postcss.config.js配置,将px转化成rem
    eslint prettier editrorconfig
    数据结构题集--集锦
  • 原文地址:https://www.cnblogs.com/sidianok/p/11970729.html
Copyright © 2011-2022 走看看