對象的定義
在C/C++中窥突,對象就是堆(Heap)內(nèi)存中的內(nèi)存實體努溃,從簡單的基本數(shù)據(jù)類型(int,float,char)到聚合的數(shù)據(jù)類型(struct)一切皆為對象,我們說基本的數(shù)據(jù)類型是簡單的對象(Simple Object)阻问,因為它僅包含數(shù)據(jù)屬性梧税。而struct級別的數(shù)據(jù)類型是完整的對象(Concrete Object),因為完整的對象具有屬性和行為兩個基本概念称近。
- 屬性就是結(jié)構(gòu)體的數(shù)據(jù)字段第队,可以是基本數(shù)據(jù)類型,指針類型刨秆、甚至是嵌套的struct類型凳谦。
- 行為又稱為方法或成員函數(shù),就是struct內(nèi)部定義的一系列函數(shù)指針。
備注:如有疑問:請移至《C++ 面向?qū)ο蟆?/a>和《C++ 多態(tài)》
Python對象的本源 PyObject
CPython是用C語言實現(xiàn)的衡未,那么用C/C++中關(guān)于對象的概念尸执,去理解Python對象也是理所當然的。先看一下CPython中關(guān)于PyObject的定義
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt; //引用計數(shù)器缓醋,和內(nèi)存回收有關(guān)
PyTypeObject *ob_type; //定義Python對象的元類信息
} PyObject;
其實整個PyObject的難點是就是第三個字段PyTyepObject,也是整個PyObject的核心,包括基本的類型信息:類名稱剔交,類型尺寸(需要分配多大的內(nèi)存)以及類綁定的方法(即綁定的函數(shù)指針)。后文會詳細談到改衩,而_PyObject_HEAD_EXTRA這個宏的定義如下
#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; \
struct _object *_ob_prev;
#define _PyObject_EXTRA_INIT 0, 0,
#else
#define _PyObject_HEAD_EXTRA
#define _PyObject_EXTRA_INIT
#endif
而以發(fā)布模型的編譯CPython源代碼的話,_PyObject_HEAD_EXTRA這段宏定義是不存在驯镊,因此PyObject的定義可以簡化為
typedef struct _object {
Py_ssize_t ob_refcnt; //引用計數(shù)器葫督,和內(nèi)存回收有關(guān)
PyTypeObject *ob_type; //定義Python對象的元類信息
} PyObject;
在Python的世界觀中一切皆為PyObject這個話怎么理解呢竭鞍?在Python語義中,每當我們實例化任意一個Python對象橄镜,在其占用的堆內(nèi)存區(qū)塊的首個字節(jié)就包含一個PyObject定義的副本偎快,除PyObject的相關(guān)內(nèi)存字節(jié)副本外,跟隨PyObject對象內(nèi)存副本之后的內(nèi)存字節(jié)是當前對象的內(nèi)存信息洽胶。舉個例子晒夹,比如PyLongObject,繼承PyVarObject,我們先看看位于Include/longintrepr.h定義
struct _longobject {
PyObject_VAR_HEAD
digit ob_digit[1];
};
而PyObject_VAR_HEAD這個宏定義實質(zhì)上是PyVarObject類型编振,而PyVarObject本來就繼承PyObject,其定義如下Include/object.h
....
#define PyObject_VAR_HEAD PyVarObject ob_base;
....
typedef struct {
PyObject ob_base;
//這是一個指向C類型的數(shù)據(jù)指針指向的堆內(nèi)存區(qū)域
//ob_size就是統(tǒng)計該堆內(nèi)存能夠容納元素個數(shù)的計數(shù)器
Py_ssize_t ob_size;
} PyVarObject;
任何一個Python對象繼承PyVarObject坐桩,表明它是一個可變長對象(或叫容器對象)未玻,其PyLongObject的完整形式的定義如下
struct _longobject{
PyObject ob_base; //PyObject的內(nèi)存副本
Py_ssize_t ob_size; //數(shù)據(jù)指針的計數(shù)器
//其實ob_digit是一個堆中的數(shù)組喝检,只不過目前指向索引1的元素
digit ob_digit[1];
} PyLongObject;
或者是這樣
struct _longobject{
Py_ssize_t ob_refcnt; //引用計數(shù)器累贤,和內(nèi)存回收有關(guān)
PyTypeObject *ob_type; //定義Python對象的元類信息
Py_ssize_t ob_size; //數(shù)據(jù)指針的計數(shù)器
//數(shù)據(jù)指針 ob_digit构哺,而ob_digit[1]標識一個digit類型的數(shù)組元素
digit ob_digit[1];
} PyLongObject;
好吧底燎!用一個類繼承圖就比較直觀
從內(nèi)存圖的角度來看蠢络,如下圖所示禾唁,PyLongObject類型的對象效览,我們說當實例化一個PyLongObject的實例,它首先要初始化PyVarObject的內(nèi)存中的數(shù)據(jù)(所有字段的默認值)荡短,我們說PyLongObject的實例持有PyVarObject的內(nèi)存副本丐枉。而PyVarObject初始化,也要初始化PyObject的內(nèi)存數(shù)據(jù)掘托。換句話說瘦锹,在Python內(nèi)部,每個堆吸納個都擁有相同的對象頭部烫映,這使對象的引用變得單一化沼本,只需一個PyObject*指針就可以任意引用一個對象。
備注:其實C++面向?qū)ο竽P痛篌w上是這么一個套路锭沟,只不過C++運行時增加了一些對象訪問控制設(shè)定抽兆,而C實現(xiàn)的PyObject是不存在所謂訪問控制設(shè)定一回事。
定長對象和變長對象
在CPython 3.x像整數(shù)類型的Python對象族淮,其具體的數(shù)字字面量ob_digit[1](注意ob_digit是一個unsigned int數(shù)組)辫红。例如
>>> n=int(1)
>>> n2=int(9999)
>>> n
1
>>> n2
9999
>>> import sys
>>> sys.getsizeof(n)
28
>>> sys.getsizeof(n2)
28
我們說像int,double這類的基本數(shù)據(jù)類型,他們不同的實例有固定長度的祝辣,我們說這些叫定長對象贴妻。而變長對象即對象的類型尺寸是可變的。例如PyStringObject蝙斜、PyListObject名惩、PyDictObject這些都是可變長對象(也叫容器對象,這和C++標準庫的容器對象非常相似了)孕荠。容器對象的最基本的特征娩鹉。其struct內(nèi)部維護著一個數(shù)據(jù)指針(指向堆中一片連續(xù)的內(nèi)存區(qū)域)攻谁,以及一個計數(shù)器ob_size就是實時統(tǒng)計該堆內(nèi)存區(qū)域有多少個數(shù)據(jù)實體。目前僅需簡單了解這些概念即可弯予。
PyTypeObject
我們說過任意一個PyObject的實例創(chuàng)建過程中戚宦,需要知道類型名,需要分配的堆內(nèi)存锈嫩,以及該對象實例配套的行為(即函數(shù)指針)受楼,這些信息均包含在PyTypeObject實例中,注意我的描述,這個關(guān)鍵字定義必須牢記呼寸。
重要概念: PyTypeObject實例是指在CPython3.x源碼中艳汽,滿足“Py<類型名稱>_Type”這樣的命名風格的結(jié)構(gòu)體初始化代碼都叫PyTypeObject實例。
例如PyLongObject等舔,對應的PyTypeObject實例就是PyLong_Type骚灸,PyListObject對應的PyTypeObject實例是PyList_Type,等等。下文會詳細談到慌植。
我們先查看一下PyTypeObject的類定義甚牲,如下所示,完整代碼見Include/cpython/object.h的第193行-274行蝶柿。
struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; //類型名稱
//內(nèi)存分配的類型尺寸
Py_ssize_t tp_basicsize, tp_itemsize;
/* Methods to implement standard operations */
destructor tp_dealloc;
Py_ssize_t tp_vectorcall_offset;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
or tp_reserved (Python 3) */
reprfunc tp_repr;
/* 標準類的匹配的方法 */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
/* 更多標準操作(此處為二進制兼容性)*/
hashfunc tp_hash;
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
setattrofunc tp_setattro;
//函數(shù)來訪問對象作為I/O緩沖器
PyBufferProcs *tp_as_buffer;
/* Flags to define presence of optional/expanded features */
unsigned long tp_flags;
const char *tp_doc; /* Documentation string */
...
//屬性描述符和子類相關(guān)信息丈钙,這里定義
//這里定義了對象的行為
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;
在PyTypeObject中有三個非常重要的字段,分別是tp_as_number交汤、tp_as_sequence雏赦、tp_as_mapping。它們分別指向PyNumberMethod芙扎、PySequenceMethods和PyMappingMethods的函數(shù)族星岗。
- PyNumberMethod:定義了Python對象的行為可以像數(shù)字類型執(zhí)行乘除加減等操作。
- PySequenceMethods:定義了Python對象作為一個順序表一樣的行為戒洼,例如list俏橘。
- PyMappingMethods:定義Python對象的關(guān)聯(lián)行為,例如dict
Python對象的類型信息
從PyTypeObject定義中起始字段是一個PyObject_VAR_HEAD,這說明PyTypeObject也是PyObject圈浇,正好說明CPython中寥掐,一切事物都是Python對象,而每個對象有其一個對應的Type磷蜀。注意:**不論什么編程語言召耘,當說一個對象是什么類型,即意味最起碼的三點信息:1.類型名稱、2.類型尺寸褐隆、3.對象內(nèi)存地址污它。
來看一個例子來展示一下什么是PyTypeObject實例
>>> class Student(object):
... pass
...
>>> type(Student)
<class 'type'>
>>> st=Student()
>>> type(st)
<class '__main__.Student'>
>>> i=int(10)
>>> type(i)
<class 'int'>
>>> type(int)
<class 'type'>
>>>
每個用戶定義的Python對象的實例可以由其類內(nèi)部關(guān)聯(lián)的PyTypeObject確定其類型(即type名稱、其對象占用堆內(nèi)存尺寸、對象內(nèi)存地址...)轨蛤,那么PyTypeObject本身也是Python對象蜜宪,其類型是什么呢?就是Type類型祥山,當然PyTypeObject的類型由PyType_Type實例來確定。具體的代碼見詳細代碼請查閱Objects/typeobject.c第3738行-3781行掉伏。
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 */
offsetof(PyTypeObject, tp_vectorcall), /* tp_vectorcall_offset */
....
(ternaryfunc)type_call, /* tp_call */
0, /* tp_str */
(getattrofunc)type_getattro, /* tp_getattro */
(setattrofunc)type_setattro, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS |
Py_TPFLAGS_HAVE_VECTORCALL, /* tp_flags */
type_doc, /* tp_doc */
(traverseproc)type_traverse, /* tp_traverse */
(inquiry)type_clear, /* tp_clear */
0, /* tp_richcompare */
offsetof(PyTypeObject, tp_weaklist), /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
type_methods, /* tp_methods */
type_members, /* tp_members */
type_getsets, /* tp_getset */
....
};
這里需要思考一個問題缝呕,當我們在解釋器接受到類似type(Student)、type(123)這些語句斧散,CPython內(nèi)部如何獲取其類型信息呢供常?我們之前說過任何一個Python對象內(nèi)存頭部保存著PyObject的內(nèi)存副本,更明確地說是引用計數(shù)器和PyTypeObject內(nèi)存副本鸡捐。在Python中所有class關(guān)鍵字的類定義都通過一個與其對應的PyTypeObject實例來創(chuàng)建該類型的對象栈暇。比如Python的int類型對應C層面的PyLongObject類,而PyLongObject的實例化由對應的PyLong_Type實例提供類型信息箍镜。如此類推源祈,還有其他常見的Python對象與PyTypeObject實例的對應關(guān)系,如下表所示
在CPython的運行時中色迂,所有內(nèi)置的數(shù)據(jù)類型已經(jīng)在C代碼中預定義了所有對應類型的PyType_Type實例了香缺,并且這些實例僅在運行時初始化一次
這里我們?nèi)匀灰訮yLongObject為例, 當一個我們實例化一個int類型的對象時歇僧,CPython內(nèi)部需要引用對應的PyLong_Type實例內(nèi)的類型信息图张。PyLong_Type實例的初始化語句見具體源代碼,見Objects/longobject.c的第5671行-5712行,從PyLong_Type源碼可知PyVarObject_HEAD_INIT(&PyType_Type诈悍,0)宏定義在編譯時已經(jīng)經(jīng)歷過如下的替換過程祸轮。
我們說的宏表達式PyVarObject_HEAD_INIT(&PyType_Type)最終等價于
{{0,0,1, &PyType_Type },0},
那么PyLong_Type實例的最終代碼形式,如下所示侥钳,
PyTypeObject PyLong_Type = {
{{0,0,1, &PyType_Type },0},
"int", /* tp_name */
offsetof(PyLongObject, ob_digit), /* tp_basicsize */
sizeof(digit), /* tp_itemsize */
....
long_to_decimal_string, /* tp_repr */
&long_as_number, /* tp_as_number */
....
(hashfunc)long_hash, /* tp_hash */
....
PyObject_GenericGetAttr, /* tp_getattro */
....
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
Py_TPFLAGS_LONG_SUBCLASS, /* tp_flags */
long_doc, /* tp_doc */
....
long_richcompare, /* tp_richcompare */
....
long_methods, /* tp_methods */
....
long_getset, /* tp_getset */
....
long_new, /* tp_new */
PyObject_Del, /* tp_free */
};
如果适袜,你上面代碼還沒有概念的話,我們通過一個實例來說明一些基本的事實慕趴,首先我們在Python解釋器交互命令行創(chuàng)建一個比較無聊的num函數(shù)痪蝇,該函數(shù)旨在其內(nèi)部實例化一個PyLongObject的實例m
可以用gdb工具執(zhí)行Python解釋器,我們通過對Python/ceval.c源碼關(guān)于LOAD_CONST指令定義的C代碼的執(zhí)行斷點冕房,
- 使用print *value命令,可以知道初始化一個PyLongObject實例時躏啰,它的ob_type字段是指向PyLong_Type實例的內(nèi)存地址。
-
通過print一下*(value->ob_type)耙册,我們得到PyLong_Type實例的所有具體細節(jié)给僵,該PyLong_Type實例的ob_type字段指向一個PyType_Type實例的內(nèi)存地址,并且tp_base字段是指向一個PyBaseObject_Type的實例。
藥不要停,如此類推我們通過print打印ob_base找到當前PyType_Type子類實例的本體帝际,通過ob_type找到上一級實例的
顯然蔓同,根據(jù)PyVarObject_HEAD_INIT(&PyType_Type,0)可知蹲诀,在PyLongObject實例化過程中,會引用PyLong_Type實例斑粱,而PyLong_Type實例也會引用PyType_Type實例。而PyType_Type實例是整個CPython類型系統(tǒng)的根脯爪,也就是說PyType_Type是所有Python對象的元類则北。我們可以通過一個內(nèi)存圖來得到一個清晰的輪廓
咦,這不就是類似C++類繼承的原理嗎?不用猜痕慢。就是尚揣,同時這也是體現(xiàn)CPython面向?qū)ο竽J街械牧硪粋€特征--多態(tài)。