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