從class對象到instance對象html
如今,咱們來看看如何經過class對象,建立instance對象python
demo1.py算法
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虛擬機類機制之自定義class(四)這一章中,咱們看到了Python虛擬機是如何執行class A語句的,如今,咱們來看看,當咱們實例化一個A對象,Python虛擬機又是如何執行的函數
a = A() //字節碼指令 22 LOAD_NAME 1 (A) 25 CALL_FUNCTION 0 28 STORE_NAME 2 (a)
在前面一節Python虛擬機類機制之自定義class(四),咱們看到在建立class對象的最後,Python執行引擎經過STORE_NAME指令,將建立好的class對象放入到local名字空間,因此在實例化class A的時候,指令"22 LOAD_NAME 1 (A)"會從新將class A對象取出,壓入到運行時棧中。以後,又是經過一個CALL_FUNCTION指令來建立instance對象。在建立完instance對象以後,再次經過STORE_NAME指令將實例對象a放入到local名字空間中。因此,這段字節碼指令序列完成以後,local名字空間如圖1-1所示post
圖1-1 建立instance對象後的local名字空間spa
在CALL_FUNCTION中,Python一樣會沿着call_function->do_call->PyObject_Call的調用路徑進入到PyObject_Call中。前面說過,所謂「調用」,就是執行對象的type所對應的class對象的tp_call操做。因此,在PyObject_Call中,Python執行引擎會尋找class對象<class A>的type中定義的tp_call操做。<class A>的type爲<type 'type'>,因此,最終將調用tp_call,在PyType_Type.tp_call中又調用了A.tp_new是用來建立instance對象3d
這裏須要特別注意,在建立<class A>這個class對象時,Python虛擬機調用PyType_Ready對<class A>進行了初始化,其中的一項動做就是繼承基類的操做,因此A.tp_new會繼承自object.tp_new。在PyBaseObject_Type中,這個操做被定義爲object_new。建立class對象和建立instance對象的不一樣之處正是在於tp_new不一樣,建立class對象,Python虛擬機使用的是tp_new,而對於instance對象,Python虛擬機使用的object_neworm
在object_new中,調用了A.tp_alloc,這個操做也是從object繼承而來的,是PyType_GenericAlloc。前面咱們提到,PyType_GenericAlloc最終將申請A.tp_basicsize+A.tp_itemsize大小的內存空間。上一節,這兩個量的計算結果爲A.tp_basicsize=PyBaseObject_Type.tp_basicsize+8=sizeof(PyObject)+8=24;A.tp_itemsize=PyBaseObject_Type.tp_itemsize=0。原來,object_new的全部工做就是申請一個24字節的內存空間htm
在申請了24字節的內存空間,回到type_call以後,因爲建立的不是class對象,而是instance對象,type_call會嘗試進行初始化的動做對象
typeobject.c
static PyObject * type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *obj; obj = type->tp_new(type, args, kwds); if (obj != NULL) { if (type == &PyType_Type && PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 && (kwds == NULL || (PyDict_Check(kwds) && PyDict_Size(kwds) == 0))) return obj; if (!PyType_IsSubtype(obj->ob_type, type)) return obj; type = obj->ob_type; if (PyType_HasFeature(type, Py_TPFLAGS_HAVE_CLASS) && type->tp_init != NULL && type->tp_init(obj, args, kwds) < 0) { Py_DECREF(obj); obj = NULL; } } return obj; }
基於<class A>建立的instance對象obj,其ob_type固然也在PyType_GenericAlloc中被設置爲指向<class A>,其tp_init在PyType_Ready時會繼承PyBaseObject_Type的object_init操做,由於A的定義中重寫了__init__,因此在fix_slot_dispatchers中,tp_init會指向slotdefs中指定的__init__對應的slot_tp_init
typeobject.c
static int slot_tp_init(PyObject *self, PyObject *args, PyObject *kwds) { static PyObject *init_str; PyObject *meth = lookup_method(self, "__init__", &init_str); PyObject *res; if (meth == NULL) return -1; res = PyObject_Call(meth, args, kwds); Py_DECREF(meth); if (res == NULL) return -1; if (res != Py_None) { PyErr_Format(PyExc_TypeError, "__init__() should return None, not '%.200s'", res->ob_type->tp_name); Py_DECREF(res); return -1; } Py_DECREF(res); return 0; }
在執行slot_tp_init時,Python虛擬機會首先經過lookup_method在class對象及其mro列表中搜索屬性__init__對應的操做,而後經過PyObject_Call調用該操做。在定義class時,重寫__init__操做,那麼搜索的結果就是咱們寫的操做,若是沒有重寫,那麼最終的結果將是調用object._init,在object_init中,Python虛擬機什麼也不作,直接返回,因此,當咱們用a = A()建立一個instance對象時,實際上沒有進行任何初始化的動做
到這裏,咱們稍微小結一下從class對象到instance對象的兩個步驟:
其中,args爲一個tupple對象,裏面包含着建立instance對象的各個參數,而kwds一般爲NULL。須要注意的是,這兩個步驟也適用於從metaclass對象建立class對象。從metaclass對象建立class對象的過程也是從一個從class對象建立instance對象
訪問instance對象中的屬性
在Python中,形如x.y或x.y()形式的表達式稱爲「屬性引用」,其中x爲對象,y爲對象的屬性。這個屬性,有可能只是簡單的數據,好比字符串或整數,也有多是成員函數這類比較複雜的東西。在class A中一共有兩個函數,一個是不須要參數的成員函數,一個是須要參數的成員函數,這裏,咱們先來看看,對於不須要參數的成員函數,其調用過程是怎樣的
a.f() //字節碼指令 31 LOAD_NAME 2 (a) 34 LOAD_ATTR 3 (f) 37 CALL_FUNCTION 0 40 POP_TOP
Python虛擬機經過指令LOAD_NAME會將local名字空間與符號a對應的instance對象壓入運行時棧中,隨後執行指令"34 LOAD_ATTR 3"是屬性訪問機制的關鍵所在,它會從<instance a>中得到與符號f對應的對象,這是個PyFunctionObject對象
ceval.c
case LOAD_ATTR: w = GETITEM(names, oparg); v = TOP(); x = PyObject_GetAttr(v, w); Py_DECREF(v); SET_TOP(x); if (x != NULL) continue; break;
其中,w爲PyStringObject對象f,而v爲運行時棧中的那個instance對象<instance a>,從<instance a>中得到f對應對象的關鍵就在PyObject_GetAttr中
object.c
PyObject * PyObject_GetAttr(PyObject *v, PyObject *name) { PyTypeObject *tp = v->ob_type; //[1]:經過tp_getattro得到屬性對應對象 if (tp->tp_getattro != NULL) return (*tp->tp_getattro)(v, name); //[2]:經過tp_getattr得到屬性對應對象 if (tp->tp_getattr != NULL) return (*tp->tp_getattr)(v, PyString_AS_STRING(name)); //[3]:屬性不存在,拋出異常 PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%.400s'", tp->tp_name, PyString_AS_STRING(name)); return NULL; }
在Python的class對象中,定義了兩個與訪問屬性相關的操做:tp_getattro和tp_getattr。其中的tp_getattro是首選的屬性訪問操做,而tp_getattr在Python中已再也不推薦使用,它們之間的區別主要是在屬性名的使用上,tp_getattro所使用的屬性名必須是一個PyStringObject對象,而tp_attr所使用的屬性名必須是一個C中的原生字符串。若是某個類型同時定義了tp_getattr和tp_getattro兩種屬性訪問操做,那麼PyObject_GetAttr將優先使用tp_getattro操做
在Python虛擬機建立<class A>時,會從PyBaseObject_Type中繼承tp_getattro——PyObject_GenericGetAttr,因此Python虛擬機在這裏會進入PyObject_GenericGetAttr。在PyObject_GenericGetAttr中,有一套複雜地肯定訪問屬性的算法,下面以a.f爲例,咱們用僞代碼看一下是如何肯定這個屬性的
# 首先尋找'f'對應的descriptor(descriptor在以後會細緻剖析) # 注意:hasattr會在<class A>的mro列表中尋找符號'f' if hasattr(A, 'f'): descriptor = A.f type = descriptor.__class__ if hasattr(type, '__get__') and (hasattr(type, '__set__') or 'f' not in a.__dict__): return type.__get__(descriptor, a, A) # 經過descriptor訪問失敗,在instance對象自身__dict__中尋找屬性 if 'f' in a.__dict__: return a.__dict__['f'] # instance對象的__dict__中找不到屬性,返回a的基類列表中某個基類裏定義的函數 # 注意:這裏的descriptor實際上指向了一個普通的函數 if descriptor: return descriptor.__get__(descriptor, a, A)
咱們經過一段代碼來驗證這個僞代碼的描述:
class A(object): def func(self): pass a = A() a.func = 1 print(a.func)
這段代碼很直觀,最後會輸出1,看上去與上面的僞代碼描述的不對啊。實際上,上面的僞代碼中有一個關鍵的概念——descriptor。在一個class中,並非隨意定義一個函數就是descriptor了,因此致使輸出結果爲1。那麼,究竟什麼纔是descriptor呢?這個會在下章解答