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