欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Python源码剖析 - Python中的整数对象

程序员文章站 2022-05-04 11:38:12
1. 不可变的PyIntObject "Python源码剖析 对象初探" 我们对 PyIntObject 已经有了初步的了解。 Python 中的对象可以分为固定长度和可变长度两种类型。除此之外,也可以按照可变和不可变进行划分。 PyIntObject 则属于长度固定且不可变的对象。相比其他的对象而 ......

Python源码剖析 - Python中的整数对象

1. 不可变的pyintobject

python源码剖析 - 对象初探 我们对 pyintobject 已经有了初步的了解。 python 中的对象可以分为固定长度和可变长度两种类型。除此之外,也可以按照可变和不可变进行划分。

pyintobject 则属于长度固定且不可变的对象。相比其他的对象而言,最简单,也最容易理解。

我们先来了解一下 pyintobject 类型的类型信息,代码如下:

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 */
};

核心代码解释:

代码 说明
pyvarobject_head_init(&pytype_type, 0) 1. 设定ob_type指向pyint_type结构的地址 2.定长
"int" 设定 tp_name
int_dealloc pyintobject对象的析构函数
int_print pyintobject对象的标准输出函数
int_compare 比较操作
int_to_decimal_string 将整数转换为数字字符串
&int_as_number 数学操作函数集合,如加减乘除等
int_hash 计算该对象的hash值
int_methods 对象成员函数的集合

2. pyintobject对象创建三种方式

关于 pyintobject 的对象创建过程,我们在python源码剖析 - 对象初探中已经做了初步的介绍,通过阅读源码我们可以发现有有以下三种方式,可以用来创建 pyintobject 对象

pyapi_func(pyobject *) pyint_fromstring(char*, char**, int);
pyapi_func(pyobject *) pyint_fromunicode(py_unicode*, py_ssize_t, int);
pyapi_func(pyobject *) pyint_fromlong(long);

也就是说,一个 pyintobject 可以来源于 string、unicode 和 long 类型的变量。

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;
}

从代码实现来看,如果是一个小整数,那么就直接增加对这个小整数对象的引用,否则,则需要从 free_list 中选取一个可用的对象,并将该对象的 ob_ival 设置为当前的数值。

接下来,我们详细介绍 pyintobject 中对小整数的处理方式。

3. pyintobject的小整数对象

在 python 中,代码直接对小整数对象的范围进行了限定,即 [-5, 257)

#ifndef nsmallposints
#define nsmallposints           257
#endif
#ifndef nsmallnegints
#define nsmallnegints           5
#endif
#if nsmallnegints + nsmallposints > 0
/* references to small integers are saved in this array so that they
   can be shared.
   the integers that are saved are those in the range
   -nsmallnegints (inclusive) to nsmallposints (not inclusive).
*/
static pyintobject *small_ints[nsmallnegints + nsmallposints];
#endif

这些小整数对象,类似于常量一样常驻内存中,并不会不释放,这样做的优点在于:

  • 使用效率高,这些小整数对象,像静态常量一样,直接拿来就可以用
  • 避免经常使用小整数导致内存操作效率降低 - 假设我们没有将小整数常驻内存,按照 python 中其他对象的处理方式来处理,必然会导致 malloc() 和 free() 的频繁调用,引起大量内存碎片,严重影响 python 的整体性

但是这样做有几个的问题:

  • 常量的设定,是一个经验值,你没办法预计在你的程序里,这个样的设置就是最优的
  • 修改代价大,如果你发现我对小整数的范围进行调整,你能做的唯一办法,就是对源码进行修改,并进行重新编译。

4. pyintobject的大整数对象

对于小整数,python 通过小整数对象池的方式来解决效率问题,那么对于其他整数对象,又是如何处理的呢。

其实与小整数类似,也是通过内存池技术,不同的是这个内存池中的数值并不是固定的,而是谁需要使用,就来申请,使用完了,则归还到池子中去。

struct _intblock {
    struct _intblock *next;
    pyintobject objects[n_intobjects];
};

typedef struct _intblock pyintblock;

static pyintblock *block_list = null;
static pyintobject *free_list = null;

static pyintobject *
fill_free_list(void)
{
    pyintobject *p, *q;
    /* python's object allocator isn't appropriate for large blocks. */
    p = (pyintobject *) pymem_malloc(sizeof(pyintblock));
    if (p == null)
        return (pyintobject *) pyerr_nomemory();
    ((pyintblock *)p)->next = block_list;
    block_list = (pyintblock *)p;
    /* link the int objects together, from rear to front, then return
       the address of the last int object in the block. */
    p = &((pyintblock *)p)->objects[0];
    q = p + n_intobjects;
    while (--q > p)
        py_type(q) = (struct _typeobject *)(q-1);
    py_type(q) = null;
    return p + n_intobjects - 1;
}

通过 block_listfree_list 两个指针来进行维护,free_list 是一个单向列表,维护 block_list 中所有可用的内存块。如果 block_list 不够用了,则调用 fill_free_list() 申请新的内存。

5. 更多内容

原文来自兔子先生网站:

查看原文 >>> python源码剖析 - python中的整数对象

如果你对python语言感兴趣,可以关注我,或者关注我的微信公众号:xtuz666