python基础
Python中一切都是对象,一个实例是一个对象,生成实例的类也是对象,描述类的元类也是对象。
TODO:类的继承链的图片
这张图里面,可以看出来,无论是内置类型int还是自定义类型dog,他们的基类都是object。而且描述他们类型的元类型都是type,object是所有类型的基类,本质上也是一个类,所以他的类型也是type。Type是所有类型的类型,本质上也是一种类型,所以它继承自object,类型是它自己。总体来看,所有类型的基类都收敛于object,所有类型的类型都是type。
PyObject
Python中的变量只是一个跟对象关联的名字,保存的其实是指向实际对象的指针,变量的赋值操作也就是一个拷贝指针的操作。
Python中对象可以分为可变对象和不可变对象;顾名思义,区别就是创建后对象的值能不能修改。也可分为定长对象和变长对象:区别就是对象大小能不能改变
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssie_t ob_refcnt; //引用计数
PyTypeObject *ob_type; //类型指针
} PyObject;
PyObject结构体是所有对象共用的部分,这其中包含一个引用计数,记录被引用次数,一个类型指针,指向对象的类型对象,比如刚才的dog实例的类型对象,就是dog类。这是一个最基础的对象结构。
PyVarObject
下面这个是变长对象,跟PyObject相比,多出一个ob_size字段,用来记录元素个数。变长比如string,list。
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size;
} PyVarObject;
PyTypeObject
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; //类型名称
Py_ssize_t tp_basicsize, tp_itemsize; //创建对象所需的内存信息
// 该类型支持的操作信息
destructor tp_dealloc;
printfunc tp_print;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
//...
struct _typeobject *tp_base; //类型的继承信息
//...
} PyTypeObject;
在创建对象的时候,不同的对象它肯定需要不同的内存大小,PyObject只提供了所有对象共有的信息,其他的一些元信息,都放在PyTypeObject这个结构体中,也就是前面说的类型对象,它来描述这个对象是什么类型,有什么继承关系,支持什么操作,需要多少内存这些信息。
Object 和 Type_Type
刚才我们还介绍了两个概念,一个是所有类的基类Object,还有一个是描述类型的类型,被称为元类型type。
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type",
sizeof(PyHeapTypeObject),
sizeof(PyMemberDef),
(destructor)type_dealloc,
//...
(reprfunc)type_repr,
(ternaryfunc)type_call,
//...
};
PyTypeObject PyBaseObject_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"object",
sizeof(PyObject),
0,
object_dealloc,
//...
object_repr,
};
Object背后的实体就是PyBaseObject_type,跟前面的类型对象对比,他没有设置tp_base字段记录继承信息,因为它要作为继承链的终点,防止进入死循环。
小结
到这里我们基本可以弄清楚python对象体系的关系。变量名指向一个实例对象,这个对象包含基本的pyobject,ob_type指向它的类型对象PyFloat_Object,这个类型对象的type字段中,指向的是类型的类型PyType_type对象,tp_base中记录着继承信息,基类是pyBaseObject_type,另外PyBaseObject_type是元类型的基类,PyType_Tpye是object的类型。
生命周期
接下来我们再了解一下对象的生命周期。
static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
PyObject *obj;
//...
obj = type->tp_new(type, args, kwds);
obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL);
if (obj == NULL)
return NULL;
//...
type = Py_TYPE(obj);
if (type->tp_init != NULL) {
int res = type->tp_init(obj, args, kwds);
if (res < 0){
assert(PyErr_Occurred());
Py_DECREF(obj);
obj = NULL;
}
else {
assert(!PyErr_Occurred());
}
}
return obj;
}
我们还记得类型对象中的tp_call字段,当对象被创建的时候会通过类型对象调用tp_call方法,这里先调用了tp_new申请内存,然后如果它有init的话会调用tp_init对对象进行初始化。这是两个比较关键的操作。
#define PyINCREF(op) (
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA
((PyObject*)(op))->ob_refcnt++)
#define Py_DECREF(op)
do{
if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA
--((PyObject*)(op))->ob_refcnt != 0)
_Py_CHECK_PRECNT(op)
else
_Py_Dealloc((PyObject *)(op));
} while (0)
对象的销毁是通过PyObject对象中的引用计数来判断的。刚才对象创建调用完tp_new方法之后,会执行增加引用计数的宏,然后init失败之后减少引用计数。这两个宏分别是Py_INCREF和Py_DECREF,可以看到都在操作pyObject中的记录引用次数的这个字段,在指针为零时还会会销毁或者回收对象。
不同的对象肯定有不一样的行为,通过行为可以把对象分成不同的类别。数值型的对象会有加减乘除等操作,序列型的对象有下标操作,关联型对象会产生约束。Python为每一个类别都定义了一个标准操作集。数值型对象比如int,float;序列型对象str,bytes,tuple,list;关联型对象dict
在类型对象PyTypeObject中,可以看到三个操作集的定义:type_as_number,type_as_sequence,type_as_mapping
typedef struct _typeobject
{
PyObject_VAR_HEAD
const char *tp_name;
Py_ssize_t tp_basicsize, tp_itemsize;
//...
PyNumberMethods *tp_as_number; //数值型操作
PySequenceMethods *tp_as_sequence; //序列型操作
PyMappingMethods *tp_as_mapping; //关联型操作
//...
PyBufferProcs *tp_as_buffer;
//...
} PyTypeObject;
static PyNumberMethods float_as_number = {
float_add,
float_sub,
float_mul,
float_rem,
float_divmod,
float_pow,
//...
};
PyTypeObject PyFloat_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"float",
sizeof(PyFloatObject),
&float_as_number, // tp_as_number
0, // tp_as_sequence
0, // tp_as_mapping
};
只要类型对象提供相关的操作集,实例对象就会有这些行为。以float为例可以看到:
在PyFloat_Tpye类型对象中,定义了数值型操作集,序列型和关联型都是空的,所以只支持数值型操作,float_as_number中也详细对应了float版本的函数,有加减乘取余,取模等等。