我们都知道Python是一门面向对象语言,不论整数、字符串、类型、函数都是一个对象。换句话说,面向对象理论中的“类”和“对象”这两个概念在python中都是通过对象来实现的。这是python最大的特点。
一切皆对象
一个整数是一个对象,一个字符串也是一个对象
a = 1
b = 'abc'
通过class关键字定义的类也是一个对象:
- 类型对象 比如整数类型
- 实例对象 比如通过整数类型实例化得到一个整数对象
int('123')
类型、对象关系
a = 1
a是一个实例对象,它的类型是整数类型
整数类型的类型是啥呢?
type(int) # <class 'type'>
输出可以看到,整数类型的类型还是一种类型。不过这个类型比较特殊,它的实例对象还是类型对象
python中还有一个特殊类型object,所有其它类型都继承与object
issubclass(int, object) # True
那么object类型是啥呢?type的类型又是啥呢?
type(object) # <class 'type'>
type(type) # <class 'type'>
可以看出type是所有类型的类型,包括它自己。
object是所有其它类型的基类,那type的基类是啥呢?
issubclass(type, object) # True
type.__base__ # <class 'object'>
type的类型是它自己,基类是object,object的类型是type,那object的基类是啥呢?我们试着输出
object.__base__ # 没有任何输出
这表明object没有基类,它已经是“终极boss”了。
总结一句话:所有类型的基类为object,所有类型的类型都是type
如何理解对象?
我们都知道,Python的底层实现为C,但C并不是一门面向对象的语言,那它的对象机制是如何实现的呢?
《Python源码剖析》中这样写道,对于人的思维来说,对象是一个比较形象的概念,而对于计算机来说,对象却是一个抽象的概念。它不能理解是一个整数还是一个字符串,它知道的一切都是字节。通常的说法,对象是数据以及基于这些数据的操作的集合。在计算机中,一个对象实际上就是一片被分配的内存空间,这些内存可能是连续的,也可能是离散的,不重要,重要的是这片内存在更高的层次上可以作为一个整体来思考,这个整体就是一个对象。
那么在Python中,对象就是为C中的结构体在堆上申请的一块内存。一般,对象是不能被静态初始化的,并且也不能在栈空间上生存。但是,类型对象例外。Python中,所有的类型对象(比如int、str、list等)都是在程序运行之前就被静态地初始化了,而不是在程序运行时动态生成。这意味着在程序执行期间,类型对象的内部结构和属性是固定的,不能被修改。
那可变长度数据的对象是怎么做的呢?其实只是在对象内维护一个指向一块可变大小的内存区域的指针。
定长对象与变长对象
Python一个对象有多大呢?我们借助标准库sys模块的getsizeof函数查看对象大小
整数对象
import sys
sys.getsizeof(1) # 28
sys.getsizeof(10000000000000000) # 32
sys.getsizeof(10000000000000000000000000000000000000) # 44
可以看到整数对象的大小跟数值有关系,大小不固定,为变长对象。
字符串对象
import sys
sys.getsizeof('') # 49
sys.getsizeof('a') # 50
sys.getsizeof('ab') # 51
可以看到字符串对象也是变长对象。
浮点数对象
import sys
sys.getsizeof(1.0) # 24
sys.getsizeof(1000000000000000000000000000000000000000.0) # 24
可以看出浮点数对象是定长对象
可变对象与不可变对象
可变对象在对象创建后,值可以更改;不可变对象在对象创建后的整个生命周期,值都不能修改。
整数对象是可变对象or不可变对象?
a = 1
id(a) # 4305443568
a += 1
id(a) # 4305443600
可以看出,整数类型是不可变类型,整数对象是不可变对象。修改整数对象时,将创建一个新对象,变量名与新对象进行绑定,旧对象无其它引用将被释放。
小知识
如何理解变量名与对象绑定呢?
a = 1
id(a) # 4305443568
b = a
id(b) # 4305443568
变量只是一个名字,变量赋值,就是让对象与名字绑在一起。
所以,在Python内部,变量只是一个名字,保存指向实际对象的指针,赋值只拷贝指针,并不拷贝指针背后的对象。
我们看一个可变对象,比如:列表
l = [1]
id(l) # 4308872448
l.append(2)
id(l) # 4308872448
PyObject-对象机制的基石
上文提到在Python中,对象就是为C中的结构体在堆上申请的一块内存,究竟内部长啥样,只能在源码中找到答案了。
PyObject 结构体定义于头文件 object.h ,代码如下:
Include/pytypedefs.h
typedef struct _object PyObject;
定义了一个名为PyObject的结构体类型,但是该结构体的具体实现并没有在这里给出
Include/object.h
struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
};
- _PyObject_HEAD_EXTRA 宏
- ob_refcnt 引用计数
- ob_type 类型指针
引用计数
对象被其它地方引用时加一,引用解除时减一;引用为0时,便可将对象回收
类型指针
指向对象的类型对象,类型对象描述实例对象的数据及行为
_PyObject_HEAD_EXTRA 宏
#ifdef Py_TRACE_REFS
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA \
PyObject *_ob_next; \
PyObject *_ob_prev;
#define _PyObject_EXTRA_INIT _Py_NULL, _Py_NULL,
#else
# define _PyObject_HEAD_EXTRA
# define _PyObject_EXTRA_INIT
#endif
如果定义了Py_TRACE_REFS,则使用_PyObject_HEAD_EXTRA宏将两个指针(_ob_next和_ob_prev)添加到每个堆分配的Python对象的开头。这些指针用于创建所有活动堆对象的双链接列表。一般不启用。如果Py_TRACE_REFS没有定义,那么_PyObject_HEAD_EXTRA宏没有作用
那变长对象的结构体是啥样呢?
变长对象,就是在PyObject基础上加入长度信息,就是PyVarObject:
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;
可以看到比PyObject只多了一个字段ob_size,用于记录元素个数。
具体对象,需要包含头部PyObject 或 PyVarObject 。 为此,头文件准备了两个宏定义,方便其他对象使用:
#define PyObject_HEAD PyObject ob_base;
#define PyObject_VAR_HEAD PyVarObject ob_base;
我们看看不变长对象,浮点对象
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
可以看到,在PyObject头部基础上,多了一个双精度浮点数
再看看变长对象,比如列表
typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item;
Py_ssize_t allocated;
} PyListObject;
可以看到变长对象,需要在PyVarObject头部基础上,用一个动态数组实现,数组存储列表包含的对象,即PyObject指针。
再讲一下初始化对象头部的宏定义。
PyObject_HEAD_INIT 一般用于定长对象
#define PyObject_HEAD_INIT(type) \
{ _PyObject_EXTRA_INIT \
1, type },
将ob_refcnt设置为1并将对象类型ob_type设置成给定类型
PyVarObject_HEAD_INIT 一般用于变长对象
#define PyVarObject_HEAD_INIT(type, size) \
{ PyObject_HEAD_INIT(type) size },
在PyObject_HEAD_INIT基础上设置ob_size。
PyTypeObject-类型的基石
在看PyObject源码时,
结构体中有对象指针PyTypeObject *ob_type;,它指向的类型对象决定了这个对象是啥类型。该结构体中还包含大量元信息,包括创建对象需要分配多少内存,对象支持哪些操作等。
Include/pytypedefs.h
typedef struct _typeobject PyTypeObject;
Include/object.h
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;
Py_ssize_t tp_vectorcall_offset;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
or tp_reserved (Python 3) */
// ......
PyTypeObject *tp_base;
// ......
};
包含PyVarObject头部,可见类型对象PyTypeObject是一个变长对象。
看代码,包含信息很多,主要看一下
- tp_name 类型名
- tp_basicsize, tp_itemsize 创建该类型对象时分配的空间大小信息
- 该类型支持的相关操作信息 tp_getattr等函数指针
- 类型的继承信息,例如 tp_base 字段指向基类对象
对象的类型由对象指向的类型对象决定,那类型对象的类型由谁决定呢?
type(1) # <class 'int'>
type(int) # <class 'type'>
int的类型为啥是type呢?PyType_Type
类型的类型
源码:Objects/typeobject.c
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type", /* tp_name */
sizeof(PyHeapTypeObject), /* tp_basicsize */
sizeof(PyMemberDef), /* tp_itemsize */
// ......
}
PyType_Type在类型机制中很重要,内建类型和用户自定义类对应的PyTypeObject对象都是通过PyType_Type创建的。它是所有类型的类型,称为元类型
源码中可以看到,tp_name的值为“type”,type的类型为type
type.__class__ # <class 'type'>
接下来我们看看类型对象与PyType_Type之间的关系,以PyFloat_Type为例
源码:Objects/floatobject.c
PyTypeObject PyFloat_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"float",
sizeof(PyFloatObject),
0,
(destructor)float_dealloc, /* tp_dealloc */
// ....
0, /* tp_base */
// ......
};
前面提到PyVarObject_HEAD_INIT用来初始化ob_refcnt 、 ob_type 以及 ob_size 三个字段。tp_name 字段初始化成类型名称 float 。ob_type指针指向PyType_Type
下图是对象运行时的图像表现
我们知道,PyObject是所有类的基类,我们看看实体是如何实现的?上面提到 tp_base 字段指向基类对象,我们看到PyFloat_Type源码中,tp_base的值为0,并没有初始化,那究竟在哪里初始化的呢?
我们在 Object/object.c 看到这样一段源码
PyStatus
_PyTypes_InitTypes(PyInterpreterState *interp)
{
if (!_Py_IsMainInterpreter(interp)) {
return _PyStatus_OK();
}
// All other static types (unless initialized elsewhere)
for (size_t i=0; i < Py_ARRAY_LENGTH(static_types); i++) {
PyTypeObject *type = static_types[i];
if (PyType_Ready(type) < 0) {
return _PyStatus_ERR("Can't initialize types");
}
if (type == &PyType_Type) {
// Sanitify checks of the two most important types
assert(PyBaseObject_Type.tp_base == NULL);
assert(PyType_Type.tp_base == &PyBaseObject_Type);
}
}
return _PyStatus_OK();
}
可以看到,这里将tp_base字段初始化成PyBaseObject_Type,这就是PyObject的实体
PyTypeObject PyBaseObject_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"object", /* tp_name */
sizeof(PyObject), /* tp_basicsize */
0, /* tp_itemsize */
object_dealloc, /* tp_dealloc */
// ......
0, /* tp_base */
// ......
};
可以看到,ob_type字段指向PyType_Type,这与我们开始输出的结果是一致的
object.__class__ # <class 'type'>
_PyTypes_InitTypes源码中看到,*assert(PyBaseObject_Type.tp_base == NULL);*不设置PyBaseObject_Type.tp_base ,这与我们之前的输出结果也是一致的,object没有基类,它已经是“终极boss”了
object.__base__ # 没有任何输出
现在我们知道了Python对象体系中的所有实体基关系,上面的图进行完善,得到
想要第一时间看到最新文章,可以关注公众号:郝同学的测开日记