![caedccef8ba15d1b153170341a61076c.png](https://i-blog.csdnimg.cn/blog_migrate/acdd703b171948f048752db17a7303e3.png)
相信所有学过Python的人都听过这样一句话:Python中,一切皆对象。一个整数是一个对象,产生这个整数的类int也是一个对象。函数是一个对象,函数所在的模块也是一个对象。最为奇妙的是,代码本身也是对象。
Python把面向对象的思想发挥的淋漓尽致,然而对于初学者来说,我们还是不清楚什么是对象?Python官方解释器是由C语言编写的,解答这个问题自然需要从C语言中寻找答案。因此,我们从C的层面来看对象长的是什么模样,究竟是三头六臂,还是烈焰红唇。
1
对象机制的核心——PyObject
对于人来说,对象是一个比较具体的概念,比如说一个人,一张书桌。而对于计算机而言,对象却是一个抽象的概念。通常的说法是,对象是数据以及操作这些数据的方法的集合。由于数据和操作数据的方法都存储在计算机内存中,你可以理解一个对象就是计算机中存储这些数据和方法的一块内存空间,而这块内存可能是连续的,也可能是不连续的。如果是不连续的,我们需要从整体上来把握。再具体一些,如果从C的层面来看,Python中的对象就是为C中的结构体在内存堆空间中申请的一块内存。
Python中有很多种类型的对象,不同类型的对象之间存在一些共性,这些共性定义在PyObject中。
从Python官网下载Python解释器的源码包,解压打开Include\object.h,我们就可以看到PyObject了,PyObject的定义如下:
//Include/object.htypedef struct _object { _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; struct _typeobject *ob_type;} PyObject;
从PyObject结构体中我们可以看到在其内部定义了一个
_PyObject_HEAD_EXTRA、一个
ob_refcnt和一个
ob_type。其中
_PyObject_HEAD_EXTRA主要使用来维护一个“双向链表”,通常不启用,我们可以不用管。我们的关注点放在
ob_refcnt和
ob_type。
- ob_refcnt:即reference count,即引用计数。
- ob_type:类型指针。
我们注意到ob_type指向了一个_typeobject的结构体,这个结构体是做什么的呢?我们说对象有三个重要特征:id、type和value。_typeobject就是用来指定一个对象的类型。这部分内容我们后面再进行分析。
现在我们看到了,Python中一个对象至少包含两部分内容,一个是引用计数,一个是类型,这些是对象的共性,然而还有一些性质虽然不是所有对象所共有,但是是大部分对象所共有的,接下来我们就会看到。
2
定长对象与变长对象
Python中根据对象所占用的内存空间大小是否固定,可以将对象分为定长对象和变长对象。定长对象如整数。变长对象如字符串、列表等。字符串、列表中有多少个元素,都无法事先确定,只能使用变长对象来进行存储,变长对象在C层面对应于PyVarObject结构体。
同样可以在object.h中找到PyVarObject结构体:
//Include/object.htypedef struct { PyObject ob_base; Py_ssize_t ob_size; /* Number of items in variable part */} PyVarObject;
可以看到,PyVarObject是对PyObject的扩展,在其基础上增加一个ob_size的字段。ob_size指明了变长对象中容纳了多少个元素。比如对于一个list实例对象,它就是一个变长对象,list实例对象中有5个元素,ob_size就等于5。
Python中的对象都是在PyVarObject或者PyObject基础上进行的扩展,例如:float的实例对象,其结构体定义为:
typedef struct { PyObject_HEAD double ob_fval;} PyFloatObject;
也就是说
float的
实例
对
象的结构体为
P
yFloatObject,
P
yFloatObject
是在PyObject基础上增加了一个双精度的ob_fval,其代表的就是该实例对象的value。
再如list实例对象,其结构体的定义为:
typedef struct { PyObject_VAR_HEAD PyObject **ob_item; Py_ssize_t allocated;} PyListObject;
list实例对象是在PyVarObject的基础之上增加了两个字段:
ob_item:指向动态数组的指针,数组保存元素对象指针;
allocated:动态数组总长度,即列表当前的容量。
float和list实例对象在介绍相应数据类型的时候再进行详细阐述。
3
类对象
除了实例化对象之外,Python中类也是一种对象,我们称之为类对象。类对象之间也有一些共性,接下来我们就来看一下。首先我们要明确,类对象作为一种对象,其基本结构应该是在PyObject基础上扩展的,具体而言python中的类对象是以PyTypeObject结构体为基础的,现在我们请出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; PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) or tp_reserved (Python 3) */ reprfunc tp_repr; PyNumberMethods *tp_as_number; PySequenceMethods *tp_as_sequence; PyMappingMethods *tp_as_mapping; hashfunc tp_hash; ternaryfunc tp_call; reprfunc tp_str; getattrofunc tp_getattro; setattrofunc tp_setattro; PyBufferProcs *tp_as_buffer; unsigned long tp_flags; const char *tp_doc; /* Documentation string */ traverseproc tp_traverse; inquiry tp_clear; richcmpfunc tp_richcompare; Py_ssize_t tp_weaklistoffset; getiterfunc tp_iter; iternextfunc tp_iternext; 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; unsigned int tp_version_tag; destructor tp_finalize;#ifdef COUNT_ALLOCS 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;#endif
PyTypeObject中的字段非常多,我们首先看到的是
PyObject_VAR_HEAD,这提示
PyTypeObject是一个变长对象。其他一些关键字段包括:
tp_name:类对象的名称,是一个char 。
tp_basicsize, tp_itemsize:创建对应实例对象时所需要的内存信息。
tp_dealloc:其实例对象执行析构函数时所作的操作。
tp_print:其实例对象被打印时所作的操作。
tp_base:继承的基类。
给PyTypeObject结构体输入不同的值,就会得到python中不同的内置类对象,例如float类对象的结构体 PyFloat_Type 如下:
PyTypeObject PyFloat_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "float", sizeof(PyFloatObject), 0, (destructor)float_dealloc, /* tp_dealloc */ 0, /* tp_vectorcall_offset */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_as_async */ (reprfunc)float_repr, /* tp_repr */ &float_as_number, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)float_hash, /* tp_hash */ 0, /* tp_call */ 0, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ float_new__doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ float_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ float_methods, /* tp_methods */ 0, /* tp_members */ float_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 */ float_new, /* tp_new */};
第二行是初始化PyTypeObject的 ob_refcnt 、ob_type 以及ob_size三个字段 ,第三行是初始化PyTypeObject的tp_name字段为float,......,这样python内置的类对象float就诞生了。
现在我们应该清楚了,一个PyTypeObject对象就是面向对象理论中类对象的实现,我们熟知的int类、float类、list类都是一个PyTypeObject结构体,都是在这个结构体基础上进行了一些初始化赋值。从PyTypeObject结构体中我们可以推测,其中存储了很多实例化对象的信息,这些信息在后面我们再详细介绍。
4
总结
现在我们可以回答本文开始的问题了,Python中的对象其实就是计算机中的一块内存空间,内存空间中存储着以PyObject结构体为基础的各类对象。介绍到这里,本文的信息量已经足够丰富,我们需要消化一下才能继续后续内容的学习。在结束本文之前,我们再来看一下python中的实例对象与类对象在内存中的形态与关系,我们以浮点数为例子:
>>>float<class 'float'>>>>e = 2.71828>>>e.__class__<class 'float'>
代码中的对象在内存中的存在形式如下:
上图中,e和float都只是名字,他们分别指向内存中两个结构体。e指向的是PyFloatObject结构体,即是float实例对象,PyFloatObject结构体时在PyObject结构体基础上增加了ob_fval字段,用来从存储value。float指向PyFloatType结构体,即float类对象,PyFloatType是在PyVarObject结构体基础上增加了很多实例化对象的元信息,如tp_name等。
需要明确的是,类对象可以用来实例化产生实例对象,因此float类对象每实例化一次,就会产生一个PyFloatObject结构体,即内存中可以有多个PyFloatObject结构体。而PyFloatType在内存中只有一份,它是在python解释器启动时,自动产生的。
![caedccef8ba15d1b153170341a61076c.png](https://i-blog.csdnimg.cn/blog_migrate/acdd703b171948f048752db17a7303e3.png)
[1] 陈儒.Python源码剖析——深度探索动态语言核心计数[M].电子工业出版社:北京,2008:1.
[2]fasionchan.Python源码深度剖析[EB/OL].https://www.imooc.com/read/76,2020.
END
![71ab66242b331a22c5bf6e4314fc3bd8.gif](https://i-blog.csdnimg.cn/blog_migrate/dbe574b144688844ee35e2e775ac0ba8.gif)
更多精彩等着你
![01818f3c4d65d141cb639bd832d0d2be.png](https://i-blog.csdnimg.cn/blog_migrate/00badc9c33fe11a5be476ffadfd03238.jpeg)