日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

python中superclass是什么_深度解析并实现python中的super(转载,好文)

發布時間:2023/12/10 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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(转载,好文)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。