Python源码剖析阅读笔记 :第一章:Python对象初探
Python对象初探
对象是python中的核心概念,Python中一切皆对象。一种内置数据类型也是一种对象,我们称之为内置类型对象(int,string,list等)。这些类型对象实现了面向对象中‘类’的概念。使用时,就可以实例化出这些内置类型对象。
第一章只需要了解python对象是如何被c实现的。python对象整个体系在第二章会有完整讲解。
1.1python内的对象
在python中,对象就是为C中的结构体在堆上申请的一块内存,一般来说,对象是不能被静态初始化的,并且也不能在栈空间上生存。唯一的例外就是类型对象,Python中所有的内建的类型对象都是被静态初始化的。
python对象创建后,内存中的大小是不变的。如果那些需要容纳可变长度数据的对象(list,dict等可变类型的实例化对象)
,只能维护一个指向一块可变大小的内存区域的指针。这样会使维护对象的工作变得简单。从对数据的操作变成了对指针的操作,工作由繁化简了。
1.1.1对象机制的基石——PyObject
在python中,所有的东西都是对象,而所有的对象都拥有一些相同的内容,这些内容在pyobject中定义。
[object.h]
typedef struct_object{
PyObject_HEAD
};
这个struct就是对象机制中的基石,从代码中可以看到,Python对象的秘密都隐藏在PyObject_HEAD这个宏中。
#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; \
struct _object *_ob_prev;
#define _PyObject_EXTRA_INIT 0, 0,
#else
#define _PyObject_HEAD_EXTRA
#define _PyObject_EXTRA_INIT
#endif
实际发布python中,PyObject的定义非常简单:
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
ob_refcnt是与python的内存管理机制有关,它实现了基于引用计数的垃圾收集机制。另一个是指向结构体的指针*ob_type,用来说明创建的对象是什么类型的一个类型对象。
定长对象和变长对象
整数对象的特殊信息是一个C中的整型变量,无论这个整数对象的值有多大,都可以保存在这个整形变量(ob_ival)中。但是并不是所有对象都可以这么定义。比如字符串。所以需要添加‘变量的个数’这一变量(ob_size)来控制个数。
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;
我们把整数对象这样不包含可变长度数据的对象成为‘定长对象’,而字符串对象这样包含可变长度数据的对象成为‘变长对象’。在python内部,每一个对象都拥有相同的对象头部。这就使得在python中,对对象的引用变得非常统一,我们只需要一个PyObject*指针就可以引用任意一个对象。
类型对象
上面大概的表述,python所有对象共有信息的定义。python对象的主要部分。当内存中分配空间,创建对象的时候,必须要知道申请多大的空间。然而它是不确定的。
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "<module>.<name>" 调试时使用输出格式<module>.<name> */
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation python对象创建时内存大小的信息 */
....
1.2.1对象的创建
有两种方式来创建python对象,第一种:python C API来创建,第二种:类型对象PYInet_Type。
Python的C API分成两类,一类成为范型的API,或者称为AOL(abstract object layer)。这类API都形如:PyObject_***。可以应用任何python对象身上。对于创建一个整数对象,我们可以采用如下表达式:PyObject* intObj = PyObject_New(PyObject, &PyInt_Type)
。
另一类是与类型相关的API,COL(Concrete Object layer)。这类通常只能作用在某一种类型的对象上,对于每一种内建对象,都会有对应的API。
例子:PyObject *intObj = PyInt_FromLong(10)
创建了一个整数为10的变量。
1.2.2对象的行为
在PyTypeObject中定义了大量的指针,这些函数指针最终都会指向某个函数,或是指向NULL。这些函数指针可以视为类型对象中所定义的操作,而这些操作直接决定着一个对象在运行时所表现的行为。
#ifdef Py_LIMITED_API
typedef struct _typeobject PyTypeObject; /* opaque */
#else
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;
PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
or tp_reserved (Python 3) */
reprfunc tp_repr;
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
/* More standard operations (here for binary compatibility) */
hashfunc tp_hash;
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
setattrofunc tp_setattro;
/* Functions to access object as input/output buffer */
PyBufferProcs *tp_as_buffer;
/* Flags to define presence of optional/expanded features */
unsigned long tp_flags;
const char *tp_doc; /* Documentation string */
/* Assigned meaning in release 2.0 */
/* call function for all accessible objects */
traverseproc tp_traverse;
/* delete references to contained objects */
inquiry tp_clear;
/* Assigned meaning in release 2.1 */
/* rich comparisons */
richcmpfunc tp_richcompare;
/* weak reference enabler */
Py_ssize_t tp_weaklistoffset;
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;
/* 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;
/* Type attribute cache version tag. Added in version 2.6 */
unsigned int tp_version_tag;
destructor tp_finalize;
#ifdef COUNT_ALLOCS
/* these must be last and never explicitly initialized */
Py_ssize_t tp_allocs;
Py_ssize_t tp_frees;
Py_ssize_t tp_maxalloc;
struct _typeobject *tp_prev;
struct _typeobject *tp_next;
#endif
} PyTypeObject;
在这些操作信息中,有三组非常重要的操作族,他们是
/* Method suites for standard classes */
PyNumberMethods *tp_as_number; /*数值对象 */
PySequenceMethods *tp_as_sequence; /*序列对象 */
PyMappingMethods *tp_as_mapping; /* 关联对象*/
对于一种类型来说,它完全可以同时定义三个函数中的所有操作。换句话说,一个对象可以即表现出数值对象的特性,也可以表现出关联对象的特性。举个例子:
class MyInt(int):
def __getitem__(self, key):
return key + str(self)
a = MyInt(1)
b = MyInt(2)
print(a + b)
print(a['key'])
结果:
看上去a['key']这样的操作时一个类似于dict这样的对象才会支持的操作。从int继承出来的Myint应该自然就是一个数值对象,重写上面的方法之后,可以视为指定MyInt在Python内部对应的PyTypeObject对象的tp_as_mapping.mp_subscript操作。最终这个Myint的实例对象可以“表现”得像一个关联对象。本质上就是在于PyTypeObject中允许一种类型同事指定三种不同行为的对象。
1.2.3 类型的类型
PyTypeObject中,定义的最开始,可以发现PyObject_VAR_HEAD,这意味着Python中的类型实际上也是一种对象。每一个对象都是对应一种类型的。而类型对象是什么类型呢?对于其他对象,可以通过与其关联的类型对象确定其类型,那么通过什么来确定一个对象是类型对象呢?
答案就是PyType_Type:
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"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 */
0, /* tp_reserved */
(reprfunc)type_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
(ternaryfunc)type_call, /* tp_call */
0, /* tp_str */
(getattrofunc)type_getattro, /* tp_getattro */
(setattrofunc)type_setattro, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS, /* tp_flags */
type_doc, /* tp_doc */
(traverseproc)type_traverse, /* tp_traverse */
(inquiry)type_clear, /* tp_clear */
0, /* tp_richcompare */
offsetof(PyTypeObject, tp_weaklist), /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
type_methods, /* tp_methods */
type_members, /* tp_members */
type_getsets, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
offsetof(PyTypeObject, tp_dict), /* tp_dictoffset */
type_init, /* tp_init */
0, /* tp_alloc */
type_new, /* tp_new */
PyObject_GC_Del, /* tp_free */
(inquiry)type_is_gc, /* tp_is_gc */
};
PyType_Type在python的类型机制中是一个至关重要的对象,所有用户自定义的class所对应的PyTypeObject对象都是通过这个对象创建的。图中显示了一般的PyType_Object和PyType_Type的关系:
<type.type>是python内部的PyType_Type,它是所有class的class,所以它在Python中被称为metaclass。
Py-nt_Type是怎么和PyType_Type联系上的呢。前面提到,python中,每一个对象都将自己的引用计数、类型信息保存在开始的部分中。为了方便对内存的初始化,python提供了几个宏。
1.3 Python对象的多态性
Python 创建一个对象比如 PyLongObject
时,会分配内存进行初始化,然后 Python 内部会用 PyObject*
变量来维护这个对象,其他对象也与此类似
所以在 Python 内部各个函数之间传递的都是一种范型指针 PyObject*
我们不知道这个指针所指的对象是什么类型,只能通过所指对象的 ob_type 域 动态进行判断,而 Python 正是通过 ob_type 实现了多态机制
比如:
void Print(PyObject* object)
{
object->ob_type->tp_print(object);
}
传进来的指针可以是int型也可以是string类型,他们都会调用他们相应的类型对象中定义的输出操作。这就是多态性的展现。
1.4引用计数
Python 通过引用计数来管理维护对象在内存中的存在与否
Python 中的每个东西都是一个对象, 都有ob_refcnt
变量,这个变量维护对象的引用计数,从而最终决定该对象的创建与销毁
在 Python 中,主要通过 Py_INCREF(op)
与Py_DECREF(op)
这两个宏 来增加和减少对一个对象的引用计数。当一个对象的引用计数减少到 0 之后, Py_DECREF
将调用该对象的tp_dealloc
来释放对象所占用的内存和系统资源;
但这并不意味着最终一定会调用 free 释放内存空间。因为频繁的申请、释放内存会大大降低 Python 的执行效率。因此 Python 中大量采用了内存对象池的技术,使得对象释放的空间归还给内存池而不是直接free,后续使用可先从对象池中获取
// Include/object.h
#define _Py_NewReference(op) ( \
_Py_INC_TPALLOCS(op) _Py_COUNT_ALLOCS_COMMA \
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \
Py_REFCNT(op) = 1)
#define Py_INCREF(op) ( \
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \
((PyObject *)(op))->ob_refcnt++)
#define Py_DECREF(op) \
do { \
PyObject *_py_decref_tmp = (PyObject *)(op); \
if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \
--(_py_decref_tmp)->ob_refcnt != 0) \
_Py_CHECK_REFCNT(_py_decref_tmp) \
else \
_Py_Dealloc(_py_decref_tmp); \
} while (0)
1.5Python对象的分类
大致分为5类。
- Fundanmental对象:类型对象
- Numeric对象:数值对象
- Sequence对象:容纳其他对象的序列集合对象
- Mapping对象:具有映射关系的对象
- Internal对象: Python虚拟机在运行时内部使用的对象
这些是对python对象的一些基本了解。