本系列作为本人@takooctopus深入学习python机制的记录,这个博客遵照着栖迟于一丘的博客上面的流程进行的,也包含我在实际查看源码时的感想,特此列出,表示感谢。
对象的创建
Python在关于对象的创建上,提供了两种方式:
- 1.C Api
- 2.通过类型创建,比如
PyLong_Type
「Objects/longobject.c」
而在C Api
中提供了两类:
- 1.一种是泛型的Api,形式如同
PyObject_Xxx
「Include/object.h」,其能够应用在任何Python对象上。 - 2.另一种是与数据类型相关的Api:
{{- code -}}
不管使用哪种模块,最终都是直接分配相应的内存。
对象的行为
我们继续去看PyTypeObject
的定义,在「Include/object.h」中我们能够看见一个关于PyTypeObject
的定义结构体:
{{- code -}}
在这整个结构体中,定义了许多函数指针,其指向了某个函数或者NULL
,这些函数指针定义了Python类型对象的操作,其会在对象运行的状态而表现出不同的行为。
我们以上面11行的代码为例:
{{- code -}}
我们继续追踪,能在前面发现定义:
{{- code -}}
明显的,这个属性最终给出了一个函数指针。
对象流程
明显的tp_new
与tp_init
定义了对象的创建于初始化操作。
而对于标准类有几个重要的方法:tp_as_number
、tp_as_sequence
和tp_as_mapping
我们以第一个属性为例,追踪找到其表示的类
PyNumberMethods
:
{{- code -}}
这一个结构体定义了当类型对象「PyTypeObject」被作为一个数值对象时所能够做的操作。
明显的nb_add
代表了数值加法操作。
顺便的,我们可以得到另外两种类型的主要类型
tp_as_sequence
对应list
tp_as_mapping
对应dict
类型对象的类型
我们可以看见类型对象「PyTypeObject」的第一个属性就是PyObject_VAR_HEAD
,这代表了这个Python对象的类型同时也是一个对象。
对其进行追踪后,我们发现其在「Include/object.h」中最开始定义了这个宏:
{{- code -}}
分别对应了一个可变对象以及基本对象「在上一篇中我们提到了基本对象」
而类型对象的类型就是它自己。
对象的多态性
对于一个Python对象来说,特别的对于Python基础对象「_object」「PyObject」来说,其有一个ob_type
属性,其中包括了Python对象的类型信息。
这里的ob_type
就是一个PyTypeObject
即我们上面讨论的类型对象。
我们的Python对象能够直接使用箭头调用即object->ob_type->
形式继续调用里面的属性
而函数之间一般通过泛型指针PyObject*
来传递
我们回头看其中的某个属性,就拿C API
中的所列出来的一样:
{{- code -}}
我们可以看到这个函数中进行了多态的判断。
对象的引用计数
在类似于C和C++以及Rust的语言中,程序员有极大的自由,能够任意的申请内存,其中的小问题可以看看这篇文章,但编程人员需要负责内存的释放问题,这其中很容易导致内存泄漏和空指针的BUG。
为了解决这些问题,现代高级语言大多采用了自己的内存管理和维护系统,像Java与C#均有其自己的垃圾回收机制。
同样的,Python也建立了其自己的垃圾回收机制。
最为明显的就是它的引用计数:
我们回去看与ob_refcnt
的值变化有关的函数,可以在下面发现Py_INCREF(op)
与Py_DECREF(op)
{{- code -}}
我们可以看见当ob_refcnt
这个引用计数减少到0时,Py_DECREF(op)
就会调用析构函数_Py_Dealloc(_py_decref_tmp)
进行内存的释放工作
{{- code -}}
一般来说如果Py_LIMITED_API
没有被定义时,_Py_Dealloc(op)
的触发会销毁对象,释放内存。
类型对象是超越引用计数规则的, 它永远不会被析构.并且类型对象是共享的, 多个int类型变量的类型对象指针都是指向同一个, 且不会被视为是类型对象的引用.
我们可以简单地验证一下
{{- code -}}
上面这段程序输出为True
和True
。
我们再看它们的id(x)
和id(y
),在我的电脑上它们id均为22941395520
,id是他们的身份唯一指定,我们也很容易看出其指向了同一个int对象。