《Python 原始碼剖析》一些理解以及勘誤筆記(2)
以下是本人閱讀此書時理解的一些筆記,包含一些影響文義的筆誤修正,當然不一定正確,貼出來一起討論。
注:此書剖析的原始碼是2.5版本,在python.org 可以找到原始碼。紙質書閱讀,pdf 貼圖。
文章篇幅太長,故切分成3部分,這是第二部分。
p248: 巢狀函式、閉包和 decorator
co_cellvars: 通常是一個tuple,儲存巢狀的作用域內使用的變數名集合;
co_freevars: 通常是一個tuple,儲存使用了的外層作用域中的變數名集合。
如下的一段Python 程式碼:
1
2 3 4 5 6 7 8 |
def get_func():
value = "inner" def inner_func(): print value return inner_func show_value = get_func() show_value() |
則py 檔案編譯出來的PyCodeObject 有3個,那麼與get_func 對應的物件中的 co_cellvars 就應該包含字串 "value",而與 inner_func
對應的PyCodeObject 物件的co_freevars 也應該有字串"value"。
閉包從建立、傳遞到使用的全過程可以用以下三幅圖演示:
inner_func 可以認為是 get_func 的區域性變數,如圖2 中 inner_func 對應的 PyFunctionObject 物件的 func_closure 指向 tuple。在inner_func 呼叫過
程中,tuple 中包含的一個個cell 物件就被放到 f_localplus 中相應的位置,當引用外層作用域符號時,一定是先到 f_localsplus 中的 free 變數區域獲
取符號對應的值。實際上 value 的值可以通過 show_value.__closure__[0].cell_contents 訪問到。使用閉包的時候需要注意返回的函式不要引用任何迴圈變數,或者後續會發生變化的變數,否則出現的情況可能與你預期不同。
在closure 技術的基礎上,Python 實現了 decorator,decorator 可以認為是 "func = should_say(func)" 的一種包裝形式。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# decorator 實現
def should_say(fn): def say(*args): print 'say something...' fn(*args) return say @should_say def func(): print 'in func' func() # 輸出結果為 # say something... # in func # 不用decorator 的實現 ... def func(): print 'in func' func = should_say(func) func() |
注意還有含參的裝飾器(再封裝一層),以及裝飾類(接收一個類,並返回一個新類)。
p264: Python 中的可呼叫性(callable)。只要一個物件對應的class 物件中實現了"__call__" 操作(更確切地說,在 Python 內部的
PyTypeObject 中,tp_call 不為空),那麼這個物件就是一個可呼叫的物件,比如:
class A(object):
def __call__(self): print 'Hello Python'
那麼 a= A() a() 會輸出'Hello Python' ;可以認為 PyA_Type 物件的 tp_call 不為空。在 c++ 看來也就是函式物件的實現。
所謂 “呼叫”,就是執行物件的 type 所對應的 class 物件的 tp_call 操作。
p268: 內建型別對應的PyTypeObject 的tp_dict 填充、descriptor
在Python 內部,存在多種 descriptor,PyType_Ready 在通過add_operators 新增了 PyTypeObject 物件中定義的一些 operator 後,
還會通過 add_methods、add_members、add_getsets 新增在PyType_Object 中定義的 tp_methods、tp_members、tp_getset 函式
集。這些 add_*** 的過程與 add_operator 類似,不過最後新增到 tp_dict 中的descriptor 就不再是PyWrapperDescrObject,而分別是
PyMethodDescrObject、PyMemberDescrObject、PyGetSetDescrObject。
注:PyWrapperDescrObject 的 ob_type 是 PyWrapperDescr_Type,PyWrapperDescr_Type 物件中的 tp_call 是wrapperdescr_call,當
Python 虛擬機器”呼叫“一個 descriptor 時,也就會呼叫 wrapperdescr_call 。
一般而言,對於一個 Python 中的物件obj,如果 obj.__class__ 對應的 class 物件中存在 __get__ 、__set__、__delete__ 三種操作,那麼 obj 可以稱
為Python 的一個 descriptor。像 PyWrapperDescr_Type 的 tp_descr_get 設定了 wrapperdescr_get,故稱 PyWrapperDescrObject 為 descriptor。
如上圖來說,實際上 mp_subscript 和 d_wrapped 都是函式指標變數,它們的值相等,都是 list_subscript 。
如下的例子重寫了list 的 '__repr__ ' 方法,則初始化完成後的 A 如下圖所示:
class A(list):
def __repr__(self): return ‘Python'
即如果沒有重寫則 A.tp_repr 沒有定義,A.tp_dict 裡面也沒有定義 '__repr__',當 a = A(); a.__repr__() 找到是在mro 列表中某個基類定義的
'__repr__' ,比如 PyList_Type 的 d_wrapped 和 tp_repr 一樣,都是 list_repr。
如果重寫了則建立時A.tp_repr 被賦值為 slot_to_repr。在 slot_to_repr 中,會尋找 '__repr__' 方法對應的 PyFunctionObject 物件,正好就找到在 A 定
義中重寫的函式。比如 A.__dict__['__repr__'] 顯示是<function __repr__ at ...>,而
list.__dict__['__repr__'] 顯示的是 <slot wrapper '__repr__' of 'list' object>。
所謂的MRO 即 Method Resolve Order,也是一個class 物件的屬性解析順序(繼承情況下),class A(list) class B(list) class C(A) class D(C, B)
則 D 的 mro 列表 是(D, C, A, B, list),儲存在 PyTypeObject.tp_mro 中,可以訪問 type.__mro__ 獲取。
基類中的子類列表儲存在 PyTypeObject.tp_subclasses 中,比如訪問 int.__subclasses__() 是 <type 'bool'>。
Python 虛擬機器在 PyType_Ready 中對各種內建型別對應的 PyTypeObject 物件進行了複雜的改造動作,包括如下:
1). 設定 ob_type 資訊,tp_base 資訊(指向一個 PyTupleObject 物件,即基類列表);
2). 填充 tp_dict(自定義型別填充的是自定義函式和class 變數);
3). 確定 mro 列表在 tp_mro;
4). 基於mro 列表從基類繼承屬性操作(自定義型別不會繼承基類屬性操作到tp_dict);
5). 設定基類的子類列表在 tp_subclasses 。
p286: 在建立類對應的 PyFrameObject 物件時,f_locals 被建立並指向一個PyDictObject 物件,包含class 變數和成員函式物件,在 Frame 回退
時 f_locals 先會被壓入到執行時棧後被彈出給前一個 Frame 的執行時棧,並作為建立 class 物件的其中一個屬性表引數,這個引數一般還包含
{'__module__' : '__main__', '__doc__' : None}。
而在函式機制中,f_locals 被設定為NULL,函式機制中的區域性變數是以一種位置引數的形式放在了運行時棧前面的那段記憶體中。
p292: slotoffset = base->basicsize; basicsize 修改為 tp_basicsize 。
p283: 建立 class 物件、建立 instance 物件
PyIntObject、PyDictObject 等物件是Python 靜態提供的,它們都具有相同的介面集合,當然有的物件可能不支援某個介面,但這並
不影響它們的所有元資訊全儲存在其型別物件 PyType_Type 中;而使用者自定義的class 物件A,其介面是動態的,不可能在
metaclass 中靜態地指定,故在利用PyType_Type 物件建立 使用者自定義 class 物件A 時還需要傳遞 (classname, bases 基類列表,
methods 屬性表[class 變數、成員函式])。
因為PyType_Type 實現了 tp_call,故我們說 '呼叫' PyType_Type 建立一個自定義class 物件,流程是
call_function --> do_call --> PyObject_Call --> tp_call(type_call) --> tp_new(type_new) --> tp_alloc,
tp_alloc 繼承自<type 'object'> 的 tp_alloc 即 PyType_GenericAlloc,最終申請的記憶體大小是
metatype->tp_basicsize + metatype->tp_itemsize,從 typeobject.c 中的定義可以看到實際上就是
sizeof(PyHeapTypeObject) + sizeof(PyMemberDef)。
而 Atype->tp_basicsize = base->tp_basicsize + 8; Atype->tp_itemsize = base->itemsize; 其中 8 是 2*sizeof(PyObject*)
Atype->tp_dictoffset = base->tp_basicsize; Atype->tp_weaklistoffset = base->tp_basicsize + 4;
如果自定義 class 重寫了 __new__, 將__new__ 對應的函式改造為 static method; Atype->tp_dict 設定為 methods 屬性dict ; 呼叫 PyType_Ready
對 class 物件進行初始化。
當通過 a=A() 這樣的表示式建立instance 物件時,即 ‘呼叫’ class 物件將建立 instance 物件,同樣沿用上面的呼叫路徑,但
PyType_Type.tp_call 中呼叫的是A.tp_new,A.tp_new 繼承自object.tp_new,在PyBaseObject_Type 中定義為object_new。
在object_new 中,呼叫了A.tp_alloc,這個操作也是繼承自object,即PyType_GenericAlloc。由上面分析可知,申請的記憶體大小為
A.tp_basicsize + A.tp_itemsize(0) ,申請完記憶體空間回到 type_call,由於建立的不是 class 物件而是 instance 物件,會嘗試呼叫
AType->tp_init 進行 instance 初始化。一般情況下我們會重寫 class A 的 '__init__' 方法,故 tp_init 不再是在 PyType_Ready 中繼承
的PyBaseObject_Type 的 object_init 操作,而是 slot_tp_init。
p296: 訪問和設定 instance 物件中的屬性
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class A(object):
name = "Python" def __init__(self): print "A::__init__" def f(self): print "A::f" def g(self, aValue): self.value = aValue print self.Value a = A() a.f() a.g(10) |
在 Python 中,形如 x.y or x.y() 形式的表示式稱為“屬性引用”,屬性可能是簡單的資料或者成員函式。PyObject_GetAttr 中呼叫 Atype->tp_getattro
操作,是繼承自 PyBaseObject_Type 的 PyObject_GenericGetAttr、對應的還有 PyObject_GenericSetAttr,下面是簡略版函式實現。
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
// a = A() a.f() // object.c
// obj=&a name = "f" PyObject * PyObject_GenericGetAttr(PyObject *obj, PyObject *name) { PyTypeObject *tp = obj->ob_type; // A PyObject *descr = NULL; PyObject *res = NULL; descrgetfunc f; Py_ssize_t dictoffset; PyObject **dictptr; ... if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } // 在 A 的 mro 列表中的基類物件的 tp_dict 中查詢 'f' 對應的 descriptor /* Inline _PyType_Lookup */ { Py_ssize_t i, n; PyObject *mro, *base, *dict; /* Look in tp_dict of types in MRO */ mro = tp->tp_mro; //tuple n = PyTuple_GET_SIZE(mro); for (i = 0; i < n; i++) { base = PyTuple_GET_ITEM(mro, i); dict = ((PyTypeObject *)base)->tp_dict; descr = PyDict_GetItem(dict, name); if (descr != NULL) break; } } f = NULL; if (descr != NULL) { f = descr->ob_type->tp_descr_get; // type = descriptor.__class__, __get__ if (f != NULL && descr->ob_type->tp_descr_set != NULL) { // __set__ res = f(descr, obj, (PyObject *)obj->ob_type); goto done; // PyObject_GenericGetAttr 返回 type.__get__(descriptor, a, A) 的執行結果 } } // 如果不是 data descriptor (type 同時定義了__set__ 和 __get__) // 在 a.__dict 中查詢 /* Inline _PyObject_GetDictPtr */ dictoffset = tp->tp_dictoffset; if (dictoffset != 0) { PyObject *dict; if (dictoffset < 0) { // 說明A 繼承自 str 這樣的變長物件,對 dictoffset 做些調整 } dictptr = (PyObject **) ((char *)obj + dictoffset); dict = *dictptr; if (dict != NULL) { res = PyDict_GetItem(dict, name); // PyObject_GenericGetAttr 返回 a.__dict__['f'] if (res != NULL) { goto done; } } } // 在 a.__dict__ 中沒有找到 if (f != NULL) { // __get__ res = f(descr, obj, (PyObject *)obj->ob_type); goto done; // PyObject_GenericGetAttr 返回 type.__get__(descriptor, a, A) 的執行結果 } if (descr != NULL) { res = descr; /* descr was already increfed above */ goto done; // PyObject_GenericGetAttr 返回 descriptor } PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%.400s'", tp->tp_name, PyString_AS_STRING(name)); done: return res; } // a.time = 2 // obj=&a, name="time", value=2 int PyObject_GenericSetAttr(PyObject *obj, PyObject *name, PyObject *value) { PyTypeObject *tp = obj->ob_type; PyObject *descr; descrsetfunc f; PyObject **dictptr; int res = -1; ... if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) goto done; } descr = _PyType_Lookup(tp, name); // inline function f = NULL; if (descr != NULL) { f = descr->ob_type->tp_descr_set; if (f != NULL) { res = f(descr, obj, value); //PyObject_GenericSetAttr 返回 type.__set__(descriptor, a, 2) 的結果 goto done; } } dictptr = _PyObject_GetDictPtr(obj); // inline function if (dictptr != NULL) { PyObject *dict = *dictptr; if (dict == NULL && value != NULL) { // a.__dict__ 還沒建立 dict = PyDict_New(); // if (dict == NULL) goto done; *dictptr = dict; } if (dict != NULL) { if (value == NULL) res = PyDict_DelItem(dict, name); // 刪除 else res = PyDict_SetItem(dict, name, value); // 設定 a.__dict__['time'] = 2 goto done; } } if (f != NULL) { res = f(descr, obj, value); //PyObject_GenericSetAttr 返回 type.__set__(descriptor, a, 2) 的結果 goto done; } if (descr == NULL) { PyErr_Format(PyExc_AttributeError, "'%.100s' object has no attribute '%.200s'", tp->tp_name, PyString_AS_STRING(name)); goto done; } PyErr_Format(PyExc_AttributeError, "'%.50s' object attribute '%.400s' is read-only", tp->tp_name, PyString_AS_STRING(name)); done: return res; } |
首先我們在 _PyObject_GetDictPtr 看到前面提過的 tp_dictoffset 的作用,如下圖所示:
注意:獲取 Atype.__dict__ 訪問的是 Atype->tp_dict; 獲取 a.__dict__ 訪問的是 上圖中的__dict__。
對於 descriptor 可以細分為如下兩種:
1). data descriptor: type 中定義了 __get__ 和 __set__ 的 descriptor;
2). non data descriptor: type 中只定義了 __get__ 的 descriptor;
在 Python 虛擬機器訪問 instance 物件時,descriptor 的一個作用是影響 Python 虛擬機器對屬性的選擇,雖然 PyObject_GenericGetAttr
中對屬性的選擇線路比較複雜,但最終效果上,可以總結如下兩條規則:
1). Python 虛擬機器按照 instance 屬性、class 屬性的順序選擇屬性,即 instance 屬性 優先於 class 屬性;
2). 如果在 class 屬性中發現同名的 data descriptor,那麼該 descriptor 會優先於 instance 屬性;
這兩條原則在對屬性進行設定時仍然會被嚴格遵守。
參照函式實現,看下面圖示程式碼的演示結果,注意:2.5 原始碼被我加了很多除錯輸出而影響觀察,故在 Python 3.3.2 下演示,Python
3.x 輸出稍有不同,如 <type 'str'> 變成 <class 'str'> 。
descriptor 改變返回值 ============ instance 屬性優先於 non data descriptor ======= data descriptor 優先於 instance 屬性
如果我們在上面 py 檔案中加入 'print A.name' 這樣的訪問 class 物件的屬性的表示式時,在 PyObject_GetAttr 中的 Typetype->tp_getattro 呼叫的是
type_getattro,而非 PyObject_GenericGetAttr,對應的還有 type_setattro,簡略程式碼如下:
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
// B.value // typeobject.c
/* This is similar to PyObject_GenericGetAttr(), but uses _PyType_Lookup() instead of just looking in type->tp_dict. */ static PyObject * type_getattro(PyTypeObject *type, PyObject *name) { PyTypeObject *metatype = type->ob_type; // PyType_Type PyObject *meta_attribute, *attribute; descrgetfunc meta_get; /* Initialize this type (we'll assume the metatype is initialized) */ if (type->tp_dict == NULL) { if (PyType_Ready(type) < 0) return NULL; } /* No readable descriptor found yet */ meta_get = NULL; /* Look for the attribute in the metatype */ meta_attribute = _PyType_Lookup(metatype, name); // PyType_Type if (meta_attribute != NULL) { meta_get = meta_attribute->ob_type->tp_descr_get; if (meta_get != NULL && PyDescr_IsData(meta_attribute)) { /* Data descriptors implement tp_descr_set to intercept * writes. Assume the attribute is not overridden in * type's tp_dict (and bases): call the descriptor now. */ return meta_get(meta_attribute, (PyObject *)type, (PyObject *)metatype); } } /* No data descriptor found on metatype. Look in tp_dict of this * type and its bases */ attribute = _PyType_Lookup(type, name); // BType if (attribute != NULL) { /* Implement descriptor functionality, if any */ descrgetfunc local_get = attribute->ob_type->tp_descr_get; if (local_get != NULL) { /* NULL 2nd argument indicates the descriptor was * found on the target object itself (or a base) */ return local_get(attribute, (PyObject *)NULL, (PyObject *)type); } return attribute; } /* No attribute found in local __dict__ (or bases): use the * descriptor from the metatype, if any */ if (meta_get != NULL) { PyObject *res; res = meta_get(meta_attribute, (PyObject *)type, (PyObject *)metatype); return res; } /* If an ordinary attribute was found on the metatype, return it now */ if (meta_attribute != NULL) { return meta_attribute; } /* Give up */ PyErr_Format(PyExc_AttributeError, "type object '%.50s' has no attribute '%.400s'", type->tp_name, PyString_AS_STRING(name)); return NULL; } // B.value = 1 static int type_setattro(PyTypeObject *type, PyObject *name, PyObject *value) { if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { PyErr_Format( PyExc_TypeError, "can't set attributes of built-in/extension type '%s'", type->tp_name); return -1; } /* XXX Example of how I expect this to be used... if (update_subclasses(type, name, invalidate_cache, NULL) < 0) return -1; */ if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0) return -1; return update_slot(type, name); } |
對比PyObject_GenericGetAttr 和 type_getattro 的 函式實現可以總結出:如果最終獲得的屬性是一個存在於 class 物件的 tp_dict 中 的 descriptor
時,返回的是 descriptor.__get__ 的呼叫結果;若 descriptor 存在於 instance 物件的 __dict__ 中,則不會呼叫其 __get__ 方法,如下圖所示。
到這裡,我們可以看到 descriptor 對 屬性的訪問影響主要在兩個方面:其一是對訪問順序的影響;其二是對訪問結果的影響,第二
種影響正是類的成員函式呼叫的關鍵。
回到前面的 py 檔案,在 class 物件 A 建立完成後,在 A.__dict__ 中儲存了一個與符號'f' 對應的 PyFunctionObject 物件。表示式 a.f()
需要先從 instance 物件 a 找到屬性 f,那麼 PyObject_GenericGetAttr 返回的是什麼物件呢?走一遍流程,回到上面程式碼註釋可以看
到返回的是 descriptor.__class__.__get__(descriptor, a, A) 的執行結果,其實 PyFunctionObject 物件的 class 物件是
PyFunction_Type,與 '__get__' 對應的 tp_descr_get 在 PyType_Ready 中被設定為了 func_descr_get,這意味著 A.f 實際上是一個
non data descriptor,在 fun_descr_get 中將 A.f 對應的 PyFunctionObject 進行了一番包裝,生成一個新的 PyMethodObject 物件。
1
2 3 4 5 6 7 |
typedef struct {
PyObject_HEAD PyObject *im_func; /* The callable object implementing the method */ PyObject *im_self; /* The instance it is bound to, or NULL */ PyObject *im_class; /* The class that asked for the method */ PyObject *im_weakreflist; /* List of weak references */ } PyMethodObject; |
其中 im_func = descriptor; im_self = a; im_class = A; 這樣將一個 PyFunctionObject 與 一個 instance 物件通過 PyMethodObject 物件
結合在一起的過程稱為成員函式的繫結,如下圖所示。
現在我們知道PyObject_GenericGetAttr 返回一個PyMethodObject 物件(ob_type 為 PyMethod_Type),a.f() 接下去的操作就是將其壓入執行時棧,
實際上 a.f 僅僅是一個帶一個位置引數的函式,故需要把 self 引數解析到執行時棧,接下去的函式呼叫操作就跟 筆記(1)的 p226 條目類似了。
類似地,a.g(10) 可以看作帶2個位置引數的函式呼叫,函式中 self.value = value; 會設定 a.__dict__ 。
這裡再補充說明下上面提到過的PyWrapperDescrObject。 PyWrapperDescr_Type 的 tp_descr_get 設定了 wrapperdescr_get,故稱
PyWrapperDescrObject 為 descriptor。舉個例子 class A(list): pass a = A() a.__repr__() ,走一遍上述程式碼流程可知PyObject_GenericGetAttr 返
回的是 PyWrapperDescr_Type.wrapperdescr_get(descriptor, a, A) 的結果,而在 wrapperdescr_get 裡面會呼叫 PyWrapper_New 建立一
個 wrapperobject(ob_type 為 wrappertype),定義如下所示,類比PyMethodObject 可以理解其作用,即 desc =descriptor; self = a,完成了一個
PyWrapperDescrObject 物件與 一個instance 物件的繫結,a.__repr__() 最終呼叫到 list_repr。
1
2 3 4 5 6 |
/* --- Wrapper object for "slot" methods --- */
typedef struct { PyObject_HEAD PyWrapperDescrObject *descr; PyObject *self; } wrapperobject; |
p308: Bound Method 和 Unbound Method
當呼叫Bound Method 時,Python 虛擬機器幫我們完成了 PyFunctionObject 物件 與 instance 物件的繫結,instance 物件 自動成為 self
引數;而呼叫 Unbound Method 時,沒有這個繫結導致 im_self 為 NULL, 我們需要自己傳入 self 引數。
故我們可以這樣呼叫 Unbound Method:a = A() A.f(a, 10); 最終更改的當然是 instance 物件 a。
p311: PyObject_GenericAlloc 修改為 PyType_GenericAlloc
p310: staticmethod
首先,下圖2種方式都可以實現 staticmethod:
實際上 staticmethod 是 <type 'staticmethod'>,staticmethod(g) 的過程就是從一個 class 物件(PyStaticMethod_Type) 建立 instance 物件的過程。
1
2 3 4 |
typedef struct {
PyObject_HEAD PyObject *sm_callable; } staticmethod; |
在申請完 staticmethod 結構體決定大小的記憶體之後,還會呼叫 __init__ 進行初始化,PyStaticMethod_Type 的 tp_init 設定為 sm_init,在函式內
原來'g' 對應的那個 PyFunctionObject 被賦給了 staticmethod 物件的 sm_callable,並在 A.__dict__ 中關聯起來。
因為在 PyStaticMethod_Type 中 tp_descr_get 指向了 sm_descr_get,故實際上 staticmethod 物件也是一個 descriptor,sm_descr_get 直接返回
sm_callable。所以當我們無論通過 a.g 還是 A.g 訪問,由於'g' 是位於 class 物件A 的 tp_dict 中的 descriptor,所有會呼叫其 __get__操作,直接返回
當時最開始與'g' 對應的那個 PyFunctionObject 物件,而不是一般成員函式返回的 PyMethodObject 物件,也就沒有了繫結self 引數的過程,所以
'g' 訪問不到 a = A(); a.__dict__ 裡面的內容,但可以訪問到 A.__dict__ 裡面的內容,如上圖所示。
Python 中的 static method 只是 descriptor 應用的一個例子,還有很多其他特性,如 class method, property 等都是應用 descriptor 的例子。
相關文章
- 《Python 原始碼剖析》一些理解以及勘誤筆記(1)Python原始碼筆記
- 《Python 原始碼剖析》一些理解以及勘誤筆記(3)Python原始碼筆記
- Python 學習筆記 - socketserver原始碼剖析Python筆記Server原始碼
- Python物件初探(《Python原始碼剖析》筆記一)Python物件原始碼筆記
- Python中的List物件(《Python原始碼剖析》筆記四)Python物件原始碼筆記
- Python中的字串物件(《Python原始碼剖析》筆記三)Python字串物件原始碼筆記
- Python中的整數物件(《Python原始碼剖析》筆記二)Python物件原始碼筆記
- YYCache原始碼筆記2原始碼筆記
- python原始碼閱讀筆記Python原始碼筆記
- Express原始碼的一些理解Express原始碼
- Java集合原始碼剖析——ArrayList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】ArrayList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】Vector原始碼剖析Java原始碼
- 【Java集合原始碼剖析】HashMap原始碼剖析Java原始碼HashMap
- 【Java集合原始碼剖析】Hashtable原始碼剖析Java原始碼
- 【Java集合原始碼剖析】TreeMap原始碼剖析Java原始碼
- python異常的一些程式碼筆記Python筆記
- 【Java集合原始碼剖析】LinkedList原始碼剖析Java原始碼
- 【Java集合原始碼剖析】LinkedHashmap原始碼剖析Java原始碼HashMap
- Laravel 原始碼筆記 2 App 服務容器Laravel原始碼筆記APP
- PLSA模型的再理解以及原始碼分析模型原始碼
- python筆記2Python筆記
- 一些概念2(私人筆記)筆記
- 誤刪了一些學習筆記筆記
- vue原始碼學習筆記2(resolveConstructorOptions)Vue原始碼筆記Struct
- 原始碼筆記 — MBProgressHUD原始碼筆記
- 對於Redux原始碼的一些理解Redux原始碼
- Hadoop2.x原始碼-編譯剖析Hadoop原始碼編譯
- epoll–原始碼剖析原始碼
- HashMap原始碼剖析HashMap原始碼
- Alamofire 原始碼剖析原始碼
- Handler原始碼剖析原始碼
- Kafka 原始碼剖析Kafka原始碼
- TreeMap原始碼剖析原始碼
- SDWebImage原始碼剖析(-)Web原始碼
- Boost原始碼剖析--原始碼
- Spring原始碼剖析9:Spring事務原始碼剖析Spring原始碼
- 深入理解 Python 虛擬機器:整型(int)的實現原理及原始碼剖析Python虛擬機原始碼