Python 对象底层实现分析

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源码剖析》

如有错漏,恳请指正。

 

 

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值