python对象的本质什么
在python界有一句话: "python一切都是对象"
int, string, dict, set, class, function, module, file等都是对象
我们知道, 在面向对象程序设计中: 对象是一个实体, 类是实体的抽象特点的定义, 包括属性和方法, 通过类实例后得到对象
对于python对象而言, 有三个问题绕不过去:
- 什么是实例化python对象的类
- python类定义了哪些属性和方法
- 怎么实例化python类, 实例化的过程中, 初始化了哪些属性
搞清楚这三个问题, 才能理解python中一切是对象
先说上面三个问题的答案:
- python的类就是C语言中定义的结构体
- 结构体中定义了类的属性和和方法
- 实例化C语言的结构体即可得到python对象
首先, 我们看一个python最基础的结构体定义:
typedef struct _typeobject {
PyObject_VAR_HEAD /* 公共头 */
const char *tp_name; /* 打印 "<module>.<name>" */
Py_ssize_t tp_basicsize, tp_itemsize; /* 定义对象的大小和对象的个数, 用于分配内存大小 */
/* 需要实现的标准操作 */
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; // 定义类似于list的操作集合
PyMappingMethods *tp_as_mapping; // 定义了类似于map操作集合
/* More standard operations (here for binary compatibility) */
hashfunc tp_hash; // 定义了对象的hash操作
ternaryfunc tp_call; // 定义call(对象)操作
reprfunc tp_str; // 定义了str(对象)的操作
getattrofunc tp_getattro; // 定义了getattr的操作
setattrofunc tp_setattro; // 定义了setattr的操作
/* 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 */
/* call function for all accessible objects */
traverseproc tp_traverse;
/* delete references to contained objects */
inquiry tp_clear;
/* 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; // 定义了__init__操作
allocfunc tp_alloc;
newfunc tp_new; // 定义了__new__操作
freefunc tp_free; /* Low-level free-memory routine */
inquiry tp_is_gc; /* For PyObject_IS_GC */
PyObject *tp_bases; // 保存所有的父类
PyObject *tp_mro; // 定义了查找父类的规则
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;
} PyTypeObject;
实例化PyTypeObject
, 我们就可以得到第一个python对象,在实例化这个机构体之前,我们还需要需要知道PyObject_VAR_HEAD 头的结构体信息
# PyObject_VAR_HEAD是一个宏定义
#define PyObject_VAR_HEAD \
PyObject_HEAD \
Py_ssize_t ob_size; # 多了一个ob_size
# define PyObject_HEAD \
int ob_refcnt; \ # 和引用计算有关
struct _typeobject *ob_type; # 敲黑板_typeobject就是PyTypeObject结构体标签, 也就是说ob_type这个指针必须指向PyTypeObject
PyObject_VAR_HEAD
告知我们三点:
PyObject_VAR_HEAD比PyObject_HEAD 多一个属性 ob_size, 它表示对象的大小, 这里涉及到python对象概念, 定长对象和非定长对象,所谓定长就是长度是固定的, 例如整数, 非定长是长度不固定, 例如字符串, python还有一个概念:可变和不可变对象
每个对象都有ob_refcnt属性,这个是对象的引用计算,用于内存优化管理, 使用
sys.getrefcount
可以获取对象的引用计数每个对象都有ob_type属性, 并且这个指针指向一个PyTypeObject, 这个属性有什么用,如何初始化复制
ob_type
是一个至关重要的属性,理解ob_type对理解python核心机制有非常大的帮助
我们先从面向对象的角度,理解类和对象的关系
In [14]: class A(object): pass
In [15]: a = A()
从面向对象的角度来讲, A
是类, a
是对象 ,我们可以说: a对象是A类的实例,即 a is_instance_of A
使用python语言描述isinstance(a, A) = True
这里要非常注意了,如果某个python对象(假设是b)的ob_type是另一个python对象(假设是B), 那么可以说B对象实例化后得到b对象
使用type函数或者class属性可以获取到一个对象的类
In [16]: type(a)
Out[16]: __main__.A
In [17]: a.__class__
Out[17]: __main__.A # 这里的输出就是tp_name的赋值
所以 对象的ob_type属性的意义是决定了该对象的"类",前文提到,python中的一切都是对象,所以A
是对象,a
也是对象
我们可以说A
对象通过实例化得到了a
对象, 这里我们又推导出python对象和面向对象开发的很大的不同:
实例化一个python对象可以得到python 另一个对象, ob_type保存的就是某个对象的"类"
理解这一点至关重要!!!!!
到目前为止,我们讲清楚了python对象的机构的头部信息,除了头部结构体信息以外,我们还需要关注这个结构体的其它信息
常见的有tp_name,tp_basicsize, tp_itemsize
, 其中 tp_basicsize
和tp_itemsize
决定了分配内存的大小
接下来就是一些方法的初始化, 我们常用对象的方法就是在这里初始化, 例如 print, new, init, string操作集合, list操作集合等
现在我们就来初始化我们的第一个python对象
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0) 的父类也是type
"type", // 初始化tp_name, type(int) 输出type就是这里定义的
sizeof(PyHeadTypeObject), // 初始化tp_basicsize
sizeof(PyMemBerDef), // 初始化tp_itemsize
...省略其他函数和属性的初始化...
}
到目前为止, 我们学会了怎么初始化PyTypeObject, 同时恭喜你,你得到大名鼎鼎的PyType_Type(type)对象, 下面统一描述为type对象
type对象是所有内置(int, string, list, object,file)对象之母,也就是说内置对象的ob_type=type, 也就是说内置对象 `is_instance_of type对象
In [23]: type(str)
Out[23]: type
In [24]: type(int)
Out[24]: type
In [25]: type(object)
Out[25]: type
In [30]: type(dict)
Out[30]: type
In [26]: type(type)
Out[26]: type
In [28]: type.__class__ # 注意
Out[28]: type
In [31]: isinstance(int,type) # 注意
Out[31]: True
这里需要特别关注的是type对象的ob_type指向的是自己, 说明了type是由type类实例化得到的
现在我们知道通过type对象可以实例化出PyInt_Type(int对象), PyString_Type(str对象), PyDict_Type(dict对象)等内置对象
以PyInt_Type的生成过程为例,详细说明内置对象的生成过程:
PyTypeObject PyInt_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0) // 初始化头部, ob_type赋值为type对象
"int", // 初始化tp_name
sizeof(PyIntObject), // 初始化tp_basicsize, 这里用到了PyIntObject这个结构体, 我们接下来会聊到
0 // 初始化tp_itemsize, 因为int对象是固定长度对象, 没有item属性, 所以是0
...省略其他函数和属性的初始化...
}
有了int对象后, 我们就可以通过这个对象实例化出来一个整数
In [32]: a = int(1) # 使用int对象,得到一个具体的int整数
In [33]: a
Out[33]: 1
In [34]: a = 1 # 更简洁的方法初始化一个int对象
通过int对象很自然的就可以得到一个整数对象,回顾上文,我们提到在python中,所有的数据都是对象,包括整数对象
那整数对象的结构体是什么呢,请接着看,整数对象的结构体如下:
typedef struct {
PyObject_HEAD # PyObject_HEAD是一个宏定义
long ob_ival; # 整数的值
} PyIntObject # PyIntObject结构体就是整数对象的结构体
可以使用python的内置函数PyInt_FromLong
初始化得到一个具体的整数
PyInt_FromLong(long ival)
{
...准备工作...
v = free_list;
free_list = (PyIntObject *)Py_TYPE(v); # 分配内存
PyObject_INIT(v, &PyInt_Type); # 注意啦,整数对象的ob_type是int对象
v->ob_ival = ival;
...其他操作...
return (PyObject *) v;
}
到这里, 我们基本可以总结:
- 初始化PyTypeObject结构体, ob_type=PyType_Type可以得到PyType_Type对象
- 初始化PyTypeObject结构体(传递的参数和创建type对象不同), ob_type=PyType_Type 实例化后可以得到PyInt_Type
- 初始化PyIntObject结构体, ob_type=PyInt_Type可以得到整数对象
In [35]: a = 1
In [36]: type(a) # 整数对象的ob_type是int
Out[36]: int
到最后, 我们可以负责的说, 初始化python对象有两个核心工作:
- 需要知道对象的结构体
- 需要指定对象的ob_type
稍微提及一下一个有趣的概念是: a.ob_type = A, 那么A对象可以称为a对象的元类(metaClass)
理解了ob_type, 相当于理解了整个python对象创建的逻辑
初始化PyStringObject对象
PyStringObject的ob_type对象是PyString_Type, 也就是说PyStringObject是PyString_Type实例化得到的
PyTypeObject PyString_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0) // 初始化头
"str", // tp_name
PyStringObject_SIZE, // tp_basicsize
sizeof(char), // tp_itemsize
string_dealloc,
(printfunc)string_print,
0,
0,
0,
string_repr,
&string_as_number, // 初始化了数字操作集合
&string_as_sequence, // 初始化了list操作集合
&string_as_mapping, // 初始化了map操作集合
(hashfunc)string_hash,
PyStringObject结构如下:
typedef struct {
PyObject_VAR_HEAD // 头部信息
long ob_shash; // 是否hash过了
int ob_sstate; // 优化相关的
char ob_sval[1]; // 指向维护实际的字符串内容的内存地址
} PyStringObject;
于是 生成一个PyStringObject对象的方法如下:
PyString_FromStringAndSize (const char *str, Py_ssize_t size)
{
op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size); // 初始化了PyStringObject
PyObject_INIT_VAR(op, &PyString_Type, size); // 使用了PyString_Type, 注意ob_size的赋值
op->ob_shash = -1;
op->ob_sstate = SSTATE_NOT_INTERNED;
}
类似的还有dict, set, map等结构都是类似于这种方式创建的
初始化指针函数
在PyTypeObject结构体中, 我们可以看到很多函数需要初始化, 这些函数初始化之后, 就可以操作结构体中的数据, PyTypeObject的函数大概分为这几类:
- 初始化操作 [tp_new, tp_init]
- 类型化操作函数[tp_as_number, tp_as_sequence, tp_as_mapping]
- 常规操作函数[tp_hash, tp_call, tp_compare, tp_iter, tp_iternext, tp_print]
- Python Attribute descriptor [tp_dict,tp_descr_get, tp_descr_set ]
- 内存管理函数[tp_allocs, tp_frees, tp_maxalloc]
在python内置类型中, int类型初始化tp_as_number, string类型初始化了tp_as_number, tp_as_sequence, tp_as_mapping
所有, 猛然惊醒, 如果我们想要int对象也具有list对象的某些行为, 只需要实现tp_as_sequence就可以
属性和方法的查找
上文提到,整数对象的结构体是:
typedef struct {
PyObject_HEAD # PyObject_HEAD是一个宏定义
long ob_ival; # 整数的值
} PyIntObject # PyIntObject结构体就是整数对象的结构体
这个结构体并没有定义整数的相关操作方法(+, -), 那整数对象是如何拥有和整数相关的方法的呢,同理string对象也有类似的疑问
答案就在ob_type中,我们知道整数对象的ob_type是int对象,int对象初始化了和整数相关的操作方法,当我们操作整数方法的时候,如果找不到,则从int对象里面去查找
这类似于面向对象中继承关系。
对象的优化
试想一下,在python项目中,需要创建int对象,string对象,dict对象,同时,还可能创建很多整数对象
python是怎么优化这么多整数对象的呢?答案是池化技术。