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

Python源码剖析 - 对象初探

程序员文章站 2022-06-24 23:48:49
01 前言 对象是 python 中最核心的一个概念,在python的世界中,一切都是对象,整数、字符串、甚至类型、整数类型、字符串类型,都是对象。 02 什么是PyObject Python 中凡事皆对象,而其中 PyObject 又是所有对象的基础,它是 Python 对象机制的核心。因为它是基 ......

Python源码剖析 - 对象初探

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 函数申请内存并创建小整数对象。

更多内容

原文来自兔子先生网站:

查看原文 >>> python源码剖析 - 对象初探

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