<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
當我們輸入這個語句的時候,Python內部是如何去建立這個物件的?
a = 1.0
物件使用完畢,銷燬的時機又是怎麼確定的呢?
下面,我們以一個基本型別float為例,來分析物件從建立到銷燬這整個生命週期中的行為。
Python是用C寫的,對外提供了API,讓使用者可以從C環境中與其互動,並且Python內部也大量使用了這些API。C API分為兩類:泛型API以及特型API。
泛型API:與型別無關,屬於抽象物件層,這類API的引數是PyObject *,即可以處理任意型別的物件。以PyObject_Print為例:
// 列印浮點物件 PyObject *fo = PyFloat_FromDouble(3.14); PyObject_Print(fo, stdout, 0); // 列印整數物件 PyObject *lo = PyLong_FromLong(100); PyObject_Print(lo, stdout, 0);
特型API:與型別相關,屬於具體物件層,這類API只能作用於某種型別的物件
Python內部一般通過兩種方法建立物件:
通過C API,多用於內建型別
以浮點型別為例,Python內部提供PyFloat_FromDouble,這是一個特型C API,在這個介面內部為PyFloatObject結構體變數分配記憶體,並初始化相關欄位:
PyObject * PyFloat_FromDouble(double fval) { PyFloatObject *op = free_list; if (op != NULL) { free_list = (PyFloatObject *) Py_TYPE(op); numfree--; } else { op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject)); if (!op) return PyErr_NoMemory(); } /* Inline PyObject_New */ (void)PyObject_INIT(op, &PyFloat_Type); op->ob_fval = fval; return (PyObject *) op; }
通過型別物件,多用於自定義型別
對於自定義型別,Python就無法事先提供C API了,這種情況下就只能通過型別物件中包含的後設資料(分配多少記憶體,如何初始化等等)來建立範例物件。
由型別物件建立範例物件是一個更通用的流程,對於內建型別,除了通過C API來建立物件意外,同樣也可以通過型別物件來建立。以浮點型別為例,我們通過型別物件float,建立了一個範例物件f:
f: float = float('3.123')
思考:既然我們可以通過型別物件來建立範例物件,那麼型別物件中應該存在相應的介面。
在PyType_Type中找到了tp_call欄位:
PyTypeObject PyType_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "type", /* tp_name */ sizeof(PyHeapTypeObject), /* tp_basicsize */ sizeof(PyMemberDef), /* tp_itemsize */ (destructor)type_dealloc, /* tp_dealloc */ // ... (ternaryfunc)type_call, /* tp_call */ // ... };
因此,float(‘3.123’)在C層面就等價於:
PyFloat_Type.ob_type.tp_call(&PyFloat_Type, args. kwargs)
這裡大家可以思考下為什麼是PyFloat_Type.ob_type——因為我們在float(‘3.14’)中是通過float這個型別物件去建立一個浮點物件,而物件的通用方法是由它對應的型別管理的,自然float的型別就是type,所以我們要找的就是type的tp_call欄位。
type_call函數的C原始碼:(只列出部分)
static PyObject * type_call(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *obj; // ... obj = type->tp_new(type, args, kwds); obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL); if (obj == NULL) return NULL; // ... type = Py_TYPE(obj); if (type->tp_init != NULL) { int res = type->tp_init(obj, args, kwds); if (res < 0) { assert(PyErr_Occurred()); Py_DECREF(obj); obj = NULL; } else { assert(!PyErr_Occurred()); } } return obj; }
其中有兩個關鍵的步驟:(這兩個步驟大家應該是很熟悉的)
總結:(以float為例)
圖示如下:
通過型別物件建立範例物件,最後會落實到呼叫type_call函數,其中儲存具體物件時,使用的是PyObject *obj,並沒有通過一個具體的物件(例如PyFloatObject)來儲存。這樣做的好處是:可以實現更抽象的上層邏輯,而不用關心物件的實際型別和實現細節。(記得當初從C語言的程式導向向Java中的物件導向過度的時候,應該就是從結構體)
以物件雜湊值計算為例,有這樣一個函數介面:
Py_hash_t PyObject_Hash(PyObject *v) { // ... }
對於浮點數物件和整數物件:
PyObject *fo = PyFloatObject_FromDouble(3.14); PyObject_Hash(fo); PyObject *lo = PyLongObject_FromLong(100); PyObject_Hash(lo);
可以看到,對於浮點數物件和整數物件,我們計算物件的雜湊值時,呼叫的都是PyObject_Hash()這個函數,但是物件型別不同,其行為是有區別的,雜湊值計算也是如此。
那麼在PyObject_Hash函數內部是如何區分的呢?
PyObject_Hash()函數具體邏輯:
Py_hash_t PyObject_Hash(PyObject *v) { PyTypeObject *tp = Py_TYPE(v); if (tp->tp_hash != NULL) return (*tp->tp_hash)(v); /* To keep to the general practice that inheriting * solely from object in C code should work without * an explicit call to PyType_Ready, we implicitly call * PyType_Ready here and then check the tp_hash slot again */ if (tp->tp_dict == NULL) { if (PyType_Ready(tp) < 0) return -1; if (tp->tp_hash != NULL) return (*tp->tp_hash)(v); } /* Otherwise, the object can't be hashed */ return PyObject_HashNotImplemented(v); }
函數會首先通過Py_TYPE找到物件的型別,然後通過型別物件的tp_hash函數指標來呼叫對應的雜湊計算函數。
即:PyObject_Hash()函數根據物件的型別,呼叫不同的函數版本,這就是多型。
除了tp_hash欄位,PyTypeObject結構體還定義了很多函數指標,這些指標最終都會指向某個函數,或者為空。我們可以把這些函數指標看作是型別物件中定義的操作,這些操作決定了對應的範例物件在執行時的行為。
雖然不同的型別物件中儲存了對應範例物件共有的行為,但是不同型別的物件也會存在一些共性。例如:整數物件和浮點數物件都支援加減乘除等擦歐總,元組物件和列表物件都支援下標操作。因此,我們以行為為分類標準,對物件進行分類:
Python以此為依據,為每個類別都定義了一個標準操作集:
如果型別物件提供了相關的操作集,則對應的範例物件就具備對應的行為:
typedef struct _typeobject { PyObject_VAR_HEAD const char *tp_name; /* For printing, in format "<module>.<name>" */ Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ // ... PyNumberMethods *tp_as_number; PySequenceMethods *tp_as_sequence; PyMappingMethods *tp_as_mapping; // ... } PyTypeObject;
以float為例,型別物件PyFloat_Type的這三個欄位是這樣初始化的:
PyTypeObject PyFloat_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "float", sizeof(PyFloatObject), // ... &float_as_number, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ // ... };
可以看到,只有tp_as_number非空,即float物件支援數值型操作,不支援序列型操作和關聯型操作。
在Python中,很多場景都涉及參照計數的調整:
參照計數是Python生命週期中很關鍵的一個知識點,後續我會用一個單獨的章節來介紹,這裡咱們先按下不表,更多關於Python物件生命週期的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45