PyObject
PyObject对象是一切python对象共有的部分,包含以下内容:
typedef struct _object{
int refcnt; // 用于保存一个对象的引用计数,当一个对象引用减为0时,将会对这个对象进行一定处理(不一定就会直接释放内存资源)
struct _typeobject *ob_type; // 指向这个对象对应类型的类型对象(类型是由这些类型对象创建的),这些类型对象中存放了这种类型的对象可以进行的各种操作(比强加减),还有类型的相关信息等
} PyObject;
PyObject是所有对象共有的头部,所以可以通过PyObject *来引用任意一个对象, 类似于C++的继承。
PyVarObject
Python中除了有对象可分为两种:定长对象(int, float ...),不定长对象(字符串等);
那么Python中除了PyObject外还有一个专门用于表示变长对象的结构体 --- PyVarObject
typedef struct{
int refcnt;
struct _typeobject *ob_type;
int ob_size; // 除了上面两个共有的属性外,ob_size用于存放变长对象容纳的元素的个数(!= 字节数)
} PyVarObject;
PyTypeObject
用于创建类型对象
比如,整数2这个对象是由PyIntObject创建的,但是int这个类型(也是个对象)是由谁创建的呢?答案就是 PyTypeObject
typedef struct _typeobject {
PyObject_VAR_HEAD // 包含上面PyVarObject中的三个成员
const char *tp_name; // 类型名(int, ....)
// 下面很多是有关相关类型对象支持的操作等信息 destructor tp_dealloc;
printfunc tp_print;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
cmpfunc tp_compare;
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; ........
} PyTypeObject;
每个类型都有相应的类型对象。类型对象相当于一个中转站,比如输出一个对象的值
先获取了ob_type也就是类型对象,然后调用相应的tp_print(不同类型数据他们的tp_print域是不一样的)。这样在操作一个对象时,不需要先把这个对象转化为相应的对象,而仅用PyObject这一块区域(包括类型对象)就可以了,因为该对象的操作定义在类型对象中,比如现在我们只知道一个PyObject *a, 要输出a的值,只需要a->ob_type->tp_print(...), 而不是要先知道a的类型(整型,字符串), 转换完(PyIntObject*)a, 然后再取出值转换完(PyIntObject*)a->val;
这样的好处就是在各函数间传递时,我们可以只用PyObject*这种泛型指针即可完成对该对象的操作。
PyType_Type
上面说了类型也是一个对象,但怎么知道这个对象是一个类型对象呢?---PyType_Type
也就是说对于普通对象,通过其对应的类型对象来确定其类型,
通过PyType_Type来确定一个对象是否是类型对象。
// PyType_Type也是由PyTypeObject创建的
PyTypeObject PyType_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0, /* ob_size */
"type", /* tp_name */
sizeof(PyHeapTypeObject), /* tp_basicsize */
sizeof(PyMemberDef), /* tp_itemsize */
(destructor)type_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
type_compare, /* tp_compare */ .........
}
所有用户自定义的class都是由这个PyType_Type来创建的,也就是Python中的metaclass。
·所有类型对象(int类型的类型对象,字符串类型的类型对象....)不会被析构, 上面说了相应的对象是由类型对象来创建的,要是类型对象都没了,谁来创建这些整数,浮点数,字符串对象呢。
PyIntObject
Python中的整数对象(不可变,定长)
对应的类型对象----PyInt_Type, 里边定义了整数对象的相关信息以及可以支持的各种操作。
typedef struct {
int refcnt; struct _typeoject *ob_type;
long ob_ival; // 用于存放整数对象的值,上面是共有的头部部分
} PyIntObject;
PyStringObject
字符串对象, 定义如下
typedef struct {
PyObject_VAR_HEAD; // 变长对象共有头部,上面已经介绍过了。
long ob_shash; // 采用内部某种算法来计算该字符串的hash值,缓存到这里,避免每次都计算
int ob_sstate; // 标记该字符串是否经过intern机制处理,下面介绍
char ob_sval[1]; // 指向字符串内容
} PyStringObject;
intern机制:可以节省内存空间及提高虚拟机运行的效率。
比如:a="Python" b="Python", 那么不采取任何措施的话,内部就要维护两份相同的字符串对象了,如果是一百个"Python"呢, 那岂不是要100个相同的对象?
为了处理这种情况,Python提供了intern机制,比如对于上面b采用了inter机制后,那么创建b的时候,会先在intered这个字典中查找是否已经存在也经过inter机制处理的相同字符串,如果找到了那么就销毁b指向的字符串对象,转而让b指向查找出的相同内容的字符串对象(a创建的"Python"对象),这时可以看到内存中只有一个"Python"字符串对象,ab都指向它,引用计数为2.
上面就是intern操作的代码,interned是一个字典,用来存放已经经过intern操作的字符串。
注意上面在进行intern处理之前a指向的"Python",与b指向的"Python"对象是不同的。
PyDIct_GetItem(intered, s); 查找interned字典中是否存在,如果存在,那么把t的引用计数加一,*p的引用计数减一(这里的t就相当于上面a指向的"Python"对象,*p就是b指向的这个临时的"Python"对象),相当于把这个临时对象“销毁“了(当然只是引用计数减一变为0了)。如果没找到,就到下面的PyDict_SetItem(), 把新创建的这个对象(b指向的"Python"对象)添加到interned中。
PyListObject:
Python的列表有一个很大的好处就是可以把任意类型的数据放进去,是怎么实现的呢?其实原因就在文章的开头。
Python中所有的对象都可以用PyObject*来指向,PyIntObject, PyStringObject, 包括这里的PyListObject都可以用PyObject*类型的指针来指向,因为他们的头部都是一样的PyObject, 其实List中并不是直接存放各类型的指针,而是存放的PyObject*指针。下面来看一下定义:
PyObject_VAR_HEAD: 变长对象共有头部,不解释了。
allcated: 申请的总内存大小(可能有申请了但没使用的)
ob_item: 这里就是上面说的存放PyObject指针的地方, 不太习惯的话,换一种形式PyObject **ob_item换成PyObject* ob_item[], 这样就很明显了,ob_item这个数组里放的都是PyObject的指针,这么说其实不是很合适,虽然是PyObject*,但实际指向的可能是PyIntObject, PyStringObject....
可能又有疑问了,既然都是PyObject*, 那怎么知道指向的是整数对象还是字符串对象呢,其实上面也已经解释了(PyObject区域中有类型对象,而类型对象中存放了该种类型数据的操作)。
PyDictObject:
搜索算法是散列表,这个对象不再说了,太麻烦,这个对象对效率要求很高,因为和Python虚拟机直接相关的,比如名字空间,还有上面的interned等都是直接使用了这种数据结构。
参考:《Python源码剖析》
如有错漏,恳请指正。