
01 前言
物件是 python 中最核心的一個概念,在python的世界中,一切都是物件,整數、字串、甚至型別、整數型別、字串型別,都是物件,
02 什么是PyObject
Python 中凡事皆物件,而其中 PyObject 又是所有物件的基礎,它是 Python 物件機制的核心,因為它是基類,而其他物件都是對它的繼承,
打開 Include/python.h 中宣告如下:
#define PyObject_HEAD \
_PyObject_HEAD_EXTRA \
Py_ssize_t ob_refcnt; \
struct _typeobject *ob_type;
typedef struct _object {
PyObject_HEAD
} PyObject;
PyObject 有兩個重要的成員物件:
- ob_refcnt - 表示參考計數,當有一個新的 PyObject * 參考該物件時候,則進行 +1 操作;同時,當這個 PyObject * 被洗掉時,該參考計數就會減小,當計數為0時,該物件就會被回收,等待記憶體被釋放,
- ob_type 記錄物件的型別資訊,這個結構體含有很多資訊,見如下代碼分析,
03 型別物件
在python中,預先定義了一些型別物件,比如 int 型別、str 型別、dict 型別等,這些我們稱之為內建型別物件,這些型別物件實作了面向物件中"類"的概念,
這些內建物件實體化之后,可以創建型別物件所對應的實體物件,比如 int 物件、str 物件、dict 物件,這些實體物件可以視為面向物件理論中的“物件"這個概念在python中的體現,
#define PyObject_VAR_HEAD \
PyObject_HEAD \
Py_ssize_t ob_size; /* Number of items in variable part */
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 */
/* Methods to implement standard operations */
destructor tp_dealloc;
printfunc tp_print;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
cmpfunc tp_compare;
reprfunc tp_repr;
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
/* Attribute descriptor and subclassing stuff */
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
struct _typeobject *tp_base;
PyObject *tp_dict;
descrgetfunc tp_descr_get;
descrsetfunc tp_descr_set;
Py_ssize_t tp_dictoffset;
initproc tp_init;
allocfunc tp_alloc;
newfunc tp_new;
freefunc tp_free; /* Low-level free-memory routine */
inquiry tp_is_gc; /* For PyObject_IS_GC */
PyObject *tp_bases;
PyObject *tp_mro; /* method resolution order */
PyObject *tp_cache;
PyObject *tp_subclasses;
PyObject *tp_weaklist;
destructor tp_del;
...
} PyTypeObject;
這當中,我們需要關注幾個重點成員變數:
- tp_name 即型別名稱,例如 'int', tuple', 'list'等,可以標準輸出
- tp_basicsize 與 tp_itemsize, 創建該物件的記憶體資訊
- 關聯操作
- 描述該型別的其他資訊
04 定長物件與變長物件
定長物件比較好理解,例如一個整數物件,無論這個數值多大,它的存盤長度是一定的,這個長度由 _typeobject 來指定,不會變化,
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
變長物件在記憶體中的長度是不一定的,所以需要 ob_size 來記錄變長部分的個數,需要注意的是,這個并不是位元組的數目,
#define PyObject_VAR_HEAD \
PyObject_HEAD \
Py_ssize_t ob_size; /* Number of items in variable part */
typedef struct {
PyObject_VAR_HEAD;
long ob_shash;
int ob_sstate;
char ob_sval[1];
/* Invariants:
* ob_sval contains space for 'ob_size+1' elements.
* ob_sval[ob_size] == 0.
* ob_shash is the hash of the string or -1 if not computed yet.
* ob_sstate != 0 iff the string object is in stringobject.c's
* 'interned' dictionary; in this case the two references
* from 'interned' to this object are *not counted* in ob_refcnt.
*/
} PyStringObject;
05 創建一個定長物件的例子
代碼如下:
a = int(10)
Python 主要做了以下操作:
- 第一步:分析需要創建的型別,如上,則是 PyInt_Type
- 第二步:根據 PyInt_Type 中的 int_new 函式來構造物件
- 第三步:識別上述代碼中的 10 為字符傳,然后呼叫 PyInt_FromString() 函式來構造
- 第四步:最后呼叫 PyInt_FromLong(long ival) 函式來進行整數物件的記憶體分配和賦值,
我們先看一下 PyInt_Type的代碼實作:
- tp_name 被賦值為“int”,這樣在 type() 函式時,就會顯示該字串
- 指定 “int” 類的關聯操作,如釋放、列印、比較等
- tp_basicsize 賦值為 sizeof(PyIntObject)
- tp_itemsize 賦值為 0
PyTypeObject PyInt_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"int",
sizeof(PyIntObject),
0,
(destructor)int_dealloc, /* tp_dealloc */
(printfunc)int_print, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
(cmpfunc)int_compare, /* tp_compare */
(reprfunc)int_to_decimal_string, /* tp_repr */
&int_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
(hashfunc)int_hash, /* tp_hash */
0, /* tp_call */
(reprfunc)int_to_decimal_string, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_INT_SUBCLASS, /* tp_flags */
int_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
int_methods, /* tp_methods */
0, /* tp_members */
int_getset, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
int_new, /* tp_new */
};
這里我們對 int_new 方法進行展開, int_new 方法就是創建函式,類似于 C++ 中的建構式,用來生成PyIntObject 代碼如下:
static PyObject *
int_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *x = NULL;
int base = -909;
static char *kwlist[] = {"x", "base", 0};
if (type != &PyInt_Type)
return int_subtype_new(type, args, kwds); /* Wimp out */
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oi:int", kwlist,
&x, &base))
return NULL;
if (x == NULL) {
if (base != -909) {
PyErr_SetString(PyExc_TypeError,
"int() missing string argument");
return NULL;
}
return PyInt_FromLong(0L);
}
if (base == -909)
return PyNumber_Int(x);
if (PyString_Check(x)) {
/* Since PyInt_FromString doesn't have a length parameter,
* check here for possible NULs in the string. */
char *string = PyString_AS_STRING(x);
if (strlen(string) != PyString_Size(x)) {
/* create a repr() of the input string,
* just like PyInt_FromString does */
PyObject *srepr;
srepr = PyObject_Repr(x);
if (srepr == NULL)
return NULL;
PyErr_Format(PyExc_ValueError,
"invalid literal for int() with base %d: %s",
base, PyString_AS_STRING(srepr));
Py_DECREF(srepr);
return NULL;
}
return PyInt_FromString(string, NULL, base);
}
#ifdef Py_USING_UNICODE
if (PyUnicode_Check(x))
return PyInt_FromUnicode(PyUnicode_AS_UNICODE(x),
PyUnicode_GET_SIZE(x),
base);
#endif
PyErr_SetString(PyExc_TypeError,
"int() can't convert non-string with explicit base");
return NULL;
}
最后通過 PyInt_FromLong 方法對新產生的物件的type資訊就行賦值為 PyInt_Type,并設定整數的具體數值,其中如果是小整數,則可以從 small_ints 陣列中直接放回,
#define N_INTOBJECTS ((BLOCK_SIZE - BHEAD_SIZE) / sizeof(PyIntObject))
#define BLOCK_SIZE 1000 /* 1K less typical malloc overhead */
#define BHEAD_SIZE 8 /* Enough for a 64-bit pointer */
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
struct _intblock {
struct _intblock *next;
PyIntObject objects[N_INTOBJECTS];
};
typedef struct _intblock PyIntBlock;
static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;
PyObject *
PyInt_FromLong(long ival)
{
register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
v = small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
#ifdef COUNT_ALLOCS
if (ival >= 0)
quick_int_allocs++;
else
quick_neg_int_allocs++;
#endif
return (PyObject *) v;
}
#endif
if (free_list == NULL) {
if ((free_list = fill_free_list()) == NULL)
return NULL;
}
/* Inline PyObject_New */
v = free_list;
free_list = (PyIntObject *)Py_TYPE(v);
(void)PyObject_INIT(v, &PyInt_Type);
v->ob_ival = ival;
return (PyObject *) v;
}
06 展開
為了性能考慮,python 中對小整數有專門的快取池,這樣就不需要每次使用小整數物件時去用 malloc 分配記憶體以及free釋放記憶體,
小整數之外的大整數怎么避免重復分配和回收記憶體呢?
Python 的方案是 PyIntBlock,PyIntBlock 這個結構就是一塊記憶體,里面保存 PyIntObject 物件,一個 PyIntBlock 默認存放 N_INTOBJECTS 物件,
PyIntBlock 鏈表通過 block_list 維護,每個block中都維護一個 PyIntObject 陣列 objects,block 的 objects 可能會有些記憶體空閑,因此需要另外用一個 free_list 鏈表串起來這些空閑的項以方便再次使用,objects 陣列中的 PyIntObject 物件通過 ob_type 欄位從后往前鏈接,
小整數的快取池最終實作也是生存在 block_list 維護的記憶體上,在 python 初始化時,會呼叫 PyInt_Init 函式申請記憶體并創建小整數物件,
更多內容
原文來自兔子先生網站:https://www.xtuz.net/detail-134.html
查看原文 >>> Python原始碼剖析 - 物件初探
如果你對Python語言感興趣,可以關注我,或者關注我的微信公眾號:xtuz666
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/179260.html
標籤:Python
下一篇:Js逆向-滑動驗證碼圖片還原
