python中superclass是什么_深度解析并实现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__, )
|? 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()
, >
1
2
3
4
5
6
7
8
9
或者:
>>> class A:
...???? pass
...
>>> a = A()
>>> su = super(A, a)
>>> su
, >
>>> type(su)
>>>
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
>>> type(test)
>>>
1
2
3
4
5
6
7
8
test是一個函數,同時又是一個function對象。所以當我們使用def定義一個函數的時候,相當于創建一個function對象。因為function實現了__call__方法,所以可以被調用:
>>> getattr(test, '__call__')
>>>
1
2
3
由于function實現了__get__方法,所以,函數對象又是一個描述器對象(descriptor):
>>> getattr(test, '__get__')
1
2
因為根據python的定義,只要實現了__get__, __set__和__delete__中的一個或多個,就認為是一個描述器。
描述器的概念和bound的概念,在模塊函數上提現不出來,但是如果一個函數定義在類中,這兩個概念會體現的很明顯。
下面我們在類中定義一個函數:
>>> class A:
...???? def test(self):
...???????? pass
...
1
2
3
4
首先驗證在類中定義的函數也是一個function對象:
>>> A.__dict__['test']
>>>
>>> type(A.__dict__['test'])
>>>
>>>
1
2
3
4
5
6
7
下面驗證在類中定義的函數也是一個描述器,也就是驗證實現了__get__方法:
>>> getattr(A.__dict__['test'], '__get__')
>>>
1
2
3
從上面的驗證可以看到,在類中定義的函數,也是一個描述器對象。所以可以認為在類中定義函數,相當于定義一個描述器。所以當我們寫下面代碼時:
class A:
def test(self):
pass
1
2
3
相當于這樣:
class A:
test = function()
1
2
下面簡單講一下描述器的特性??聪旅娴拇a:
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> <__main__.person object at> zhang
NameDesc.__get__: <__main__.namedesc object at> <__main__.person object at>
zhang
NameDesc.__get__: <__main__.namedesc object at> None
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
>>>
>>> A.test is A.__dict__['test']
True
>>>
>>> a = A()
>>> a.test
>
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)
>>> A.test.__get__(None, A) == A.test
True
>>>
>>> A.test.__get__(a, A)
>
>>> 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
輸出結果為:
(, , , )
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
總結
以上是生活随笔為你收集整理的python中superclass是什么_深度解析并实现python中的super(转载,好文)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《AutoCAD 2013中文版从入门到
- 下一篇: 每天工作忙,学会python自动收发邮件