forked from flaggo/python3-source-code-analysis
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch_plus_index.json
More file actions
1 lines (1 loc) · 82 KB
/
search_plus_index.json
File metadata and controls
1 lines (1 loc) · 82 KB
1
{"./":{"url":"./","title":"前言","keywords":"","body":"介绍 本项目致力于对 Python 3.7 的源码分析,深度参考陈儒大大的《Python 源码剖析》,编写 Python 3 的版本。 希望各位 Python 爱好者能参与其中,一起探索 Python 魔法背后的奥秘! 使用 您可以直接访问 在线版,或者根据以下步骤访问本地版。 前置条件 您的系统上需要安装好 node (会自带npm)。 使用 make 或者使用 npm 命令去构建 使用 make 命令的方式构建: 若您可使用 make 命令,简单执行如下命令进行初始化: make init 执行如下命令运行服务端: make run 使用 npm 命令的方式构建: 若您不能使用 make 命令,或想直接使用 npm 命令,执行如下命令进行初始化: 安装项目依赖: npm install 执行如下命令运行服务端: npm run serve 访问 直接访问 http://localhost:4000 即可查看本书内容。 Roadmap 大体按照《Python 源码剖析》中的目录结构进行编写。依次介绍 Python 源码基本信息、内建对象和虚拟机。 [x] 章节 [x] 序章 [x] 前言 [x] Python 源代码的组织 [x] Windows 环境下编译 Python [x] UNIX/Linux 环境下编译 Python [x] 修改 Python 源码 [ ] Python 内建对象 [x] Python 对象初探 [x] Python 整数对象 [ ] Python 字符串 对象 [x] Python List 对象 [x] Python Dict 对象 [x] Python Set 对象 [ ] 实现简版 Python [ ] Python 虚拟机 [ ] Python 编译结果 [ ] Python 虚拟机框架 [ ] 虚拟机一般表达式 [ ] Python 虚拟机控制流 [ ] Python 虚拟机函数机制 [ ] Python 运行环境初始化 [ ] Python 模块加载机制 [ ] Python 多线程机制 [ ] Python 内存管理机制 Copyright © FlagGo 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-03-23 19:36:27 "},"preface/code-organization/":{"url":"preface/code-organization/","title":"Python 源代码的组织","keywords":"","body":"Python 源代码的组织 源代码下载 方式 1:GitHub Python 源代码可以在 GitHub 上方便的获取,执行: git clone https://github.com/python/cpython.git git checkout v3.7.0 即可获取 Python 3.7.0 版本的代码。 方式 2:Python 官方网站 访问 https://www.python.org/downloads/release/python-370/ ,下拉至页面最下方,可选择下载 tarball 源码包。 亦可执行: wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz 即可获取 Python 3.7.0 版本的代码。 目录结构 进入源码目录,我们可以看到该目录下主要 包含以下文件(夹): . ├── Doc ├── Grammar ├── Include ├── LICENSE ├── Lib ├── Mac ├── Makefile.pre.in ├── Misc ├── Modules ├── Objects ├── PC ├── PCbuild ├── Parser ├── Programs ├── Python ├── README.rst ├── Tools ├── aclocal.m4 ├── config.guess ├── config.sub ├── configure ├── configure.ac ├── install-sh ├── m4 ├── pyconfig.h.in └── setup.py 其中: Include 目录:包含了 Python 提供的所有头文件,如果用户需要自己用 C 或 C++来编写自定义模块扩展 Python,那么就需要用到这里提供的头文件。 Lib 目录:包含了 Python 自带的所有标准库,且都是用 Python 语言编写的。 Modules 目录:包含了所有用 C 语言编写的模块,比如 math、hashlib 等。它们都是那些对速度要求非常严格的模块。而相比而言,Lib 目录下则是存放一些对速度没有太严格要求的模块,比如 os。 Parser 目录:包含了 Python 解释器中的 Scanner 和 Parser 部分,即对 Python 源代码进行词法分析和语法分析的部分。除此以外,此目录还包含了一些有用的工具,这些工具能够根据 Python 语言的语法自动生成 Python 语言的词法和语法分析器,与 YACC 非常类似。 Objects 目录:包含了所有 Python 的内建对象,包括整数、list、dict 等。同时,该目录还包括了 Python 在运行时需要的所有的内部使用对象的实现。 Python 目录:包含了 Python 解释器中的 Compiler 和执行引擎部分,是 Python 运行的核心所在。 PCbuild 目录:包含了 Visual Studio 2003 的工程文件,研究 Python 源代码就从这里开始(本书将采用 Visual Studio 2017 对 Python 进行编译)。 Programs 目录:包含了 Python 二进制可执行文件的源码。 Copyright © FlagGo 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-03-23 19:36:27 "},"preface/windows-build/":{"url":"preface/windows-build/","title":"Windows 环境下编译 Python","keywords":"","body":"Windows 环境下编译 Python 在 Windows 环境下编译 Python 可完全通过界面操作,主要分为两个步骤: 环境准备 编译 环境准备 在 Python 3.6 及之后的版本可以使用微软的 Visual Studio 2017 进行编译,选择社区版就足够了。 在下载完成后,需要注意安装环节的选项。由于 Python 3.7 所使用的 Windows SDK 的版本为 10.0.17134.0, 所以需要选择安装该 SDK,如下图所示: 编译 进入 Python 源码根目录,打开 PCbuild\\pcbiuld.sln 解决方案,而后进行一些设置: 在左侧的解决方案目录的顶端,右键选择“属性”,以打开属性界面(如下图所示)。 由于我们只是研究 Python 的核心部分,可以选择不编译标准库和外部依赖,在“配置属性”->“配置”中仅勾选 python 和 pythoncore,然后点击“确定”(如下图所示)。 此外,默认情况下的编译设置是 Debug、32 位,您也可以根据自己的需求调整成 Release 或 64 位。 在左侧的解决方案目录中选择 python,右键选择“生成”,以进行编译: 编译结束后,生成的文件存放在PCbuild\\win32目录下(如下图所示),打开python_d即可打开新生成的 Python 3.7 解释器。 更多内容 更多关于在 Windows 上进行编译和开发 Python 的内容见官方指南 Copyright © FlagGo 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-03-23 19:36:27 "},"preface/unix-linux-build/":{"url":"preface/unix-linux-build/","title":"UNIX/Linux 环境下编译 Python","keywords":"","body":"UNIX/Linux 环境下编译 Python 在 UNIX/Linux 环境下编译 Python 较为简单,主要分为两个步骤: 环境准备(准备 Python 所依赖的必要环境) 编译、安装 环境准备 常规操作系统中 编译 Python 前通常需要在系统上安装以下库: gcc // 编译工具 zlib // 压缩、解压相关库 libffi // Python 所以来的用于支持 C 扩展的库 openssl // 安全套接字层密码库,Linux 中通常已具备 不同的发行版,安装方式和包名称也不尽相同。 对于 Debian/Ubuntu,执行: sudo apt install -y zlib1g zlib1g-dev libffi-dev openssl libssl-dev 对于 RedHat/CentOS/Fedora,执行: yum install -y zlib zlib-devel libffi-devel openssl openssl-devel 对于 macOS,执行: xcode-select --install 运行于 Docker 的操作系统中 Docker 版的 Linux 发行版可能会有较多的库未安装,除了安装上一小节提及的库外,其他缺失库可根据情况自行安装: bzip2 // 压缩库 readline // GNU Readline 是一个软件库,它为使用命令行界面(如 Bash)的交互式程序提供了行编辑和历史功能 sqlite // 由 C 编写的小型数据库 libuuid // 跨平台的开源的 uuid 操作库 gdbm // 小型的数据库系统 xz // 压缩解压工具 tk-devel // 图形用户界面开发工具 对于 Debian/Ubuntu,执行: sudo apt-get install bzip2 libbz2-dev sqlite3 libsqlite3-dev libreadline6 libreadline6-dev libgdbm-dev uuid-dev tk-dev 对于 RedHat/CentOS/Fedora,执行: yum install bzip2 bzip2-devel readline-devel sqlite-devel libuuid-devel gdbm-devel xz-devel tk-devel 编译、安装 进入 Python 源码根目录,执行以下命令: ./configure make make install Python 将会被编译,并安装在默认目录中。若您希望将 Python 安装在特定目录,则需要在一开始修改 configure 命令为: ./configure --prefix= 在指定目录中: bin 目录 存放的是可执行文件 include 目录 存放的是 Python 源码的头文件 lib 目录 存放的是 Python 标准库 lib/python3.7/config-3.7m-{platform} 目录 存放的是 libpython3.7m.a,该静态库用于使用 C 语言进行扩展。{platform} 代表平台,比如在 Mac OS 上为 “darwin”,在 Linux 上为 “x86_64-linux-gnu” share 目录 存放的是帮助等文件 默认情况下,编译的 Python 是静态链接(libpython3.7m.a)。如果希望编译的 Python 是动态链接(libpython3.7m.so),则需要在一开始修改configure 命令为: ./configure --enable-shared 如需重新编译,请首先执行: make clean 再执行本节开头处的命令即可。 Copyright © FlagGo 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-03-23 19:36:27 "},"preface/modify-code/":{"url":"preface/modify-code/","title":"修改 Python 源码","keywords":"","body":"修改 Python 源码 在源代码中 Print 在接下来研究源码的过程中,我们可能会对某些语句的逻辑感到好奇,需要输出中间结果。 这就需要借助 Python C API 中打印对象的接口: 源文件:Objects/object.c int PyObject_Print(PyObject *op, FILE *fp, int flags) 比如,我们希望在解释器交互界面中打印整数值的时候输出一段字符串,则我们可以修改如下函数: 源文件:Objects/longobject.c static PyObject * long_to_decimal_string(PyObject *aa) { PyObject *str = PyUnicode_FromString(\"I am always before int\"); PyObject_Print(str, stdout, 0); printf(\"\\n\"); PyObject *v; if (long_to_decimal_string_internal(aa, &v, NULL, NULL, NULL) == -1) return NULL; return v; } 函数实现中的前 3 行为我们加入的代码,其中: PyUnicode_FromString 用于把 C 中的原生字符数组转换为出 Python 中的字符串(Unicode)对象 PyObject_Print 则将转换好的字符串对象打印至我们指定的标准输出(stdout) 对 Python 重新进行编译,在 Unix 上可执行: make && make bininstall 运行编译后的 Python,输入 print 语句即可看到我们希望的结果: >>> print(1) 'I am always before int' 1 Copyright © FlagGo 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-03-23 19:36:27 "},"objects/object/":{"url":"objects/object/","title":"Python 对象初探","keywords":"","body":"Python 对象初探 在 Python 的世界一切皆对象,不论是整数,还是字符串,甚至连类型、函数等都是一种对象。 对象的分类 以下是 Python 对象的大致的一个分类 Fundamental 对象: 类型对象 Numeric 对象: 数值对象 Sequence 对象: 容纳其他对象的序列集合对象 Mapping 对象: 类似 C++中的 map 的关联对象 Internal 对象: Python 虚拟机在运行时内部使用的对象 对象机制的基石 PyObject 对于初学者来说这么多类型的对象怎么学?别着急,我们后续章节会解答。 在开始我们的学习之旅之前,我们要先认识一个结构体PyObject,可以说 Python 的对象机制就是基于PyObject拓展开来的,所以我们先看看PyObject 到底长什么样。 源文件:Include/object.h // Include/object.h #define _PyObject_HEAD_EXTRA \\ struct _object *_ob_next; \\ struct _object *_ob_prev; typedef struct _object { _PyObject_HEAD_EXTRA // 双向链表 垃圾回收 需要用到 Py_ssize_t ob_refcnt; // 引用计数 struct _typeobject *ob_type; // 指向类型对象的指针,决定了对象的类型 } PyObject; Python 中的所有对象都拥有一些相同的内容,而这些内容就定义在PyObject中, PyObject 包含 一个用于垃圾回收的双向链表,一个引用计数变量 ob_refcnt 和 一个类型对象指针ob_type 定长对象与变长对象 Python 对象除了前面提到的那种分类方法外,还可以分为定长对象和变长对象这两种形式。 变长对象都拥有一个相同的内容 PyVarObject,而 PyVarObject也是基于PyObject扩展的。 从代码中可以看出PyVarObject比PyObject多出了一个用于存储元素个数的变量ob_size。 源文件:Include/object.h // Include/object.h typedef struct _object { _PyObject_HEAD_EXTRA Py_ssize_t ob_refcnt; struct _typeobject *ob_type; } PyObject; typedef struct { PyObject ob_base; Py_ssize_t ob_size; /* Number of items in variable part */ } PyVarObject; 类型对象 前面我们提到了PyObject 的 对象类型指针struct _typeobject *ob_type,它指向的类型对象就决定了一个对象是什么类型的。 这是一个非常重要的结构体,它不仅仅决定了一个对象的类型,还包含大量的元信息, 包括创建对象需要分配多少内存,对象都支持哪些操作等等。 接下来我们看一下struct _typeobject代码 在 PyTypeObject 的定义中包含许多信息,主要分类以下几类: 类型名, tp_name, 主要用于 Python 内部调试用 创建该类型对象时分配的空间大小信息,即 tp_basicsize 和 tp_itemsize 与该类型对象相关的操作信息(如 tp_print 这样的函数指针) 一些对象属性 源文件:Include/object.h // Include/object.h typedef struct _typeobject { PyObject_VAR_HEAD const char *tp_name; /* For printing, in format \".\" */ // 类型名 Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ // 创建该类型对象分配的内存空间大小 // 一堆方法定义,函数和指针 /* Methods to implement standard operations */ 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; // 序列对象操作 PyMappingMethods *tp_as_mapping; // 字典对象操作 // 更多标准操作 /* More standard operations (here for binary compatibility) */ hashfunc tp_hash; ternaryfunc tp_call; reprfunc tp_str; getattrofunc tp_getattro; setattrofunc tp_setattro; ...... } PyTypeObject; 类型的类型 在 PyTypeObjet 定义开始有一个宏PyOject_VAR_HEAD,查看源码可知 PyTypeObjet 是一个变长对象 源文件:Include/object.h // Include/object.h #define PyObject_VAR_HEAD PyVarObject ob_base; 对象的类型是由该对象指向的 类型对象 决定的,那么类型对象的类型是由谁决定的呢? 对于其他对象,可以通过与其关联的类型对象确定其类型,那么通过什么来确定一个对象是类型对象呢? 答案就是 PyType_Type 源文件:Objects/typeobject.c // 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 在类型机制中至关重要,所有用户自定义 class 所 对应的 PyTypeObject 对象都是通过 PyType_Type创建的 接下来我们看 PyLong_Type 是怎么与 PyType_Type 建立联系的。 前面提到,在 Python 中,每一个对象都将自己的引用计数、类型信息保存在开始的部分中。 为了方便对这部分内存初始化,Python 中提供了几个有用的宏: 源文件:Include/object.h // Include/object.h #ifdef Py_TRACE_REFS #define _PyObject_EXTRA_INIT 0, 0, #else #define _PyObject_EXTRA_INIT #endif #define PyObject_HEAD_INIT(type) \\ { _PyObject_EXTRA_INIT \\ 1, type }, 这些宏在各种内建类型对象的初始化中被大量使用。 以PyLong_Type为例,可以清晰的看到一般的类型对象和PyType_Type之间的关系 源文件:Objects/longobject.c // Objects/longobject.c PyTypeObject PyLong_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) \"int\", /* tp_name */ offsetof(PyLongObject, ob_digit), /* tp_basicsize */ sizeof(digit), /* tp_itemsize */ ...... }; 下图是对象运行时的图像表现 对象的创建 Python 创建对象有两种方式 范型 API 或称为 AOL (Abstract Object Layer) 这类 API 通常形如PyObject_XXX这样的形式。可以应用在任何 Python 对象上, 如PyObject_New。创建一个整数对象的方式 PyObject* longobj = PyObject_New(Pyobject, &PyLong_Type); 与类型相关的 API 或称为 COL (Concrete Object Layer) 这类 API 通常只能作用于某一种类型的对象上,对于每一种内建对象 Python 都提供了这样一组 API。例如整数对象,我们可以利用如下的 API 创建 PyObject *longObj = PyLong_FromLong(10); 对象的行为 在 PyTypeObject 中定义了大量的函数指针。这些函数指针可以视为类型对象中 所定义的操作,这些操作直接决定着一个对象在运行时所表现出的行为,比如 PyTypeObject 中的 tp_hash 指明了该类型对象如何生成其hash值。 在PyTypeObject的代码中,我们还可以看到非常重要的三组操作族 PyNumberMethods *tp_as_number PySequenceMethods *tp_as_sequence PyMappingMethods *tp_as_mapping PyNumberMethods 的代码如下 源文件:Include/object.h // Include/object.h typedef PyObject * (*binaryfunc)(PyObject *, PyObject *); typedef struct { binaryfunc nb_matrix_multiply; binaryfunc nb_inplace_matrix_multiply; ...... } PyNumberMethods; PyNumberMethods 定义了一个数值对象该支持的操作。一个数值对象如 整数对象,那么它的类型对象 PyLong_Type中tp_as_number.nb_add 就指定了它进行加法操作时的具体行为。 在以下代码中可以看出PyLong_Type中的tp_as_number项指向的是long_as_number 源文件:Objects/longobject.h // Objects/longobject.c static PyNumberMethods long_as_number = { (binaryfunc)long_add, /*nb_add*/ (binaryfunc)long_sub, /*nb_subtract*/ (binaryfunc)long_mul, /*nb_multiply*/ ...... }; PyTypeObject PyLong_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) \"int\", /* tp_name */ offsetof(PyLongObject, ob_digit), /* tp_basicsize */ sizeof(digit), /* tp_itemsize */ long_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ long_to_decimal_string, /* tp_repr */ &long_as_number, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ ...... }; PySequenceMethods *tp_as_sequence 和 PyMappingMethods *tp_as_mapping的分析与PyNumberMethods *tp_as_number 相同,大家可以自行查阅源码 对象的多态性 Python 创建一个对象比如 PyLongObject 时,会分配内存进行初始化,然后 Python 内部会用 PyObject* 变量来维护这个对象,其他对象也与此类似 所以在 Python 内部各个函数之间传递的都是一种范型指针 PyObject* 我们不知道这个指针所指的对象是什么类型,只能通过所指对象的 ob_type 域 动态进行判断,而 Python 正是通过 ob_type 实现了多态机制 考虑以下的 calc_hash 函数 Py_hash_t calc_hash(PyObject* object) { Py_hash_t hash = object->ob_type->tp_hash(object); return hash; } 如果传递给 calc_hash 函数的指针是一个 PyLongObject*,那么它会调用 PyLongObject 对象对应的类型对象中定义的 hash 操作tp_hash,tp_hash可以在PyTypeObject中找到, 而具体赋值绑定我们可以在 PyLong_Type 初始化代码中看到绑定的是long_hash函数 源文件:Objects/longobject.c // Objects/longobject.c PyTypeObject PyLong_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) \"int\", /* tp_name */ ... (hashfunc)long_hash, /* tp_hash */ ... }; 如果指针是一个 PyUnicodeObject*,那么就会调用 PyUnicodeObject 对象对应的类型对象中定义的 hash 操作,查看源码可以看到 实际绑定的是 unicode_hash函数 源文件:Objects/unicodeobject.c // Objects/unicodeobject.c PyTypeObject PyUnicode_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) \"str\", /* tp_name */ ... (hashfunc) unicode_hash, /* tp_hash*/ ... }; 引用计数 Python 通过引用计数来管理维护对象在内存中的存在与否 Python 中的每个东西都是一个对象, 都有ob_refcnt 变量,这个变量维护对象的引用计数,从而最终决定该对象的创建与销毁 在 Python 中,主要通过 Py_INCREF(op)与Py_DECREF(op) 这两个宏 来增加和减少对一个对象的引用计数。当一个对象的引用计数减少到 0 之后, Py_DECREF将调用该对象的tp_dealloc来释放对象所占用的内存和系统资源; 但这并不意味着最终一定会调用 free 释放内存空间。因为频繁的申请、释放内存会大大降低 Python 的执行效率。因此 Python 中大量采用了内存对象池的技术,使得对象释放的空间归还给内存池而不是直接free,后续使用可先从对象池中获取 源文件:Include/object.h // Include/object.h #define _Py_NewReference(op) ( \\ _Py_INC_TPALLOCS(op) _Py_COUNT_ALLOCS_COMMA \\ _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \\ Py_REFCNT(op) = 1) #define Py_INCREF(op) ( \\ _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \\ ((PyObject *)(op))->ob_refcnt++) #define Py_DECREF(op) \\ do { \\ PyObject *_py_decref_tmp = (PyObject *)(op); \\ if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \\ --(_py_decref_tmp)->ob_refcnt != 0) \\ _Py_CHECK_REFCNT(_py_decref_tmp) \\ else \\ _Py_Dealloc(_py_decref_tmp); \\ } while (0) Copyright © FlagGo 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-03-23 19:36:27 "},"objects/long-object/":{"url":"objects/long-object/","title":"Python 整数对象","keywords":"","body":"Python 整数对象 CPython2 的整数对象 有 PyIntObject 和 PyLongObject 这两种类型, CPython3 只保留了 PyLongObject 在 源文件:Objects/longobject.c 的第三行有这么一句话 XXX The functional organization of this file is terrible 可见这个变化不是一蹴而就的,有比较艰辛的过程,大家有兴趣可以去挖掘一下 PyLongObject 源文件:Include/longobject.h // longobject.h typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */ 源文件:Include/longintrepr.h // longintrepr.h /* Long integer representation. The absolute value of a number is equal to 一个数的绝对值等价于下面的表达式 SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i) Negative numbers are represented with ob_size 从源码可以看出 PyLongObject 是变长对象 类型对象 PyLong_Type 源文件:Objects/longobject.c // Objects/longobject.c PyTypeObject PyLong_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) \"int\", /* tp_name */ offsetof(PyLongObject, ob_digit), /* tp_basicsize */ sizeof(digit), /* tp_itemsize */ long_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_reserved */ long_to_decimal_string, /* tp_repr */ &long_as_number, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ (hashfunc)long_hash, /* tp_hash */ 0, /* tp_call */ long_to_decimal_string, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_LONG_SUBCLASS, /* tp_flags */ long_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ long_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ long_methods, /* tp_methods */ 0, /* tp_members */ long_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 */ long_new, /* tp_new */ PyObject_Del, /* tp_free */ }; 创建整数对象 从 PyLong_Type 可以看出,创建一个整数对象的入口函数为 long_new 源文件:Objects/clinic/longobject.c.h // Objects/clinic/longobject.c.h /*[clinic input] preserve [clinic start generated code]*/ static PyObject * long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase); static PyObject * long_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) { PyObject *return_value = NULL; static const char * const _keywords[] = {\"\", \"base\", NULL}; static _PyArg_Parser _parser = {\"|OO:int\", _keywords, 0}; PyObject *x = NULL; PyObject *obase = NULL; if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, &x, &obase)) { goto exit; } return_value = long_new_impl(type, x, obase); exit: return return_value; } 具体实现在 long_new_impl 源文件:Objects/longobject.c // Objects/longobject.c /*[clinic input] @classmethod int.__new__ as long_new x: object(c_default=\"NULL\") = 0 / base as obase: object(c_default=\"NULL\") = 10 [clinic start generated code]*/ static PyObject * long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase) /*[clinic end generated code: output=e47cfe777ab0f24c input=81c98f418af9eb6f]*/ { Py_ssize_t base; if (type != &PyLong_Type) return long_subtype_new(type, x, obase); /* Wimp out */ if (x == NULL) { if (obase != NULL) { PyErr_SetString(PyExc_TypeError, \"int() missing string argument\"); return NULL; } return PyLong_FromLong(0L); } if (obase == NULL) return PyNumber_Long(x); base = PyNumber_AsSsize_t(obase, NULL); if (base == -1 && PyErr_Occurred()) return NULL; if ((base != 0 && base 36) { PyErr_SetString(PyExc_ValueError, \"int() base must be >= 2 and 从 long_new_impl 函数可以看出有如下几种情况 x == NULL 且 obase != NULL 调用 PyLong_FromLong obase 为 NULL 调用 PyNumber_Long x 和 obase 都不为 NULL PyUnicode 调用 PyLong_FromUnicodeObject,最终调用 PyLong_FromString PyByteArray/PyBytes 调用_PyLong_FromBytes,最终调用 PyLong_FromString 小整数对象 一些整数在一开始就会被初始化一直留存,当再次使用直接从小整数对象池中获取,不用频繁的申请内存。 默认的小整数范围是 [-5, 257) 源文件:Objects/longobject.c // Objects/longobject.c #ifndef NSMALLPOSINTS #define NSMALLPOSINTS 257 #endif #ifndef NSMALLNEGINTS #define NSMALLNEGINTS 5 #endif #if NSMALLNEGINTS + NSMALLPOSINTS > 0 /* Small integers are preallocated in this array so that they can be shared. The integers that are preallocated are those in the range -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive). */ static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; #ifdef COUNT_ALLOCS Py_ssize_t quick_int_allocs, quick_neg_int_allocs; #endif static PyObject * get_small_int(sdigit ival) { PyObject *v; assert(-NSMALLNEGINTS = 0) quick_int_allocs++; else quick_neg_int_allocs++; #endif return v; } #define CHECK_SMALL_INT(ival) \\ do if (-NSMALLNEGINTS 宏 CHECK_SMALL_INT 会检查传入的数是否在小整数范围内,如果是直接返回。 可以在创建或复制整数对象等函数中找到 CHECK_SMALL_INT 的身影,以下只列出了 PyLong_FromLong,就不一一列举了 源文件:Objects/longobject.c // Object/longobject.c PyObject * PyLong_FromLong(long ival) { PyLongObject *v; unsigned long abs_ival; unsigned long t; /* unsigned so >> doesn't propagate sign bit */ int ndigits = 0; int sign; CHECK_SMALL_INT(ival); ... } 小整数初始化 源文件:Objects/longobject.c // Objects/longobject.c int _PyLong_Init(void) { #if NSMALLNEGINTS + NSMALLPOSINTS > 0 int ival, size; PyLongObject *v = small_ints; for (ival = -NSMALLNEGINTS; ival ob_digit[0] == (digit)abs(ival)); } else { (void)PyObject_INIT(v, &PyLong_Type); } Py_SIZE(v) = size; v->ob_digit[0] = (digit)abs(ival); } #endif _PyLong_Zero = PyLong_FromLong(0); if (_PyLong_Zero == NULL) return 0; _PyLong_One = PyLong_FromLong(1); if (_PyLong_One == NULL) return 0; /* initialize int_info */ if (Int_InfoType.tp_name == NULL) { if (PyStructSequence_InitType2(&Int_InfoType, &int_info_desc) 整数的存储结构 源文件:Objects/longobject.c 在 long_to_decimal_string_internal中添加如下代码并重新编译安装 // Objects/longobject.c static int long_to_decimal_string_internal(PyObject *aa, PyObject **p_output, _PyUnicodeWriter *writer, _PyBytesWriter *bytes_writer, char **bytes_str) { PyLongObject *scratch, *a; PyObject *str = NULL; Py_ssize_t size, strlen, size_a, i, j; digit *pout, *pin, rem, tenpow; int negative; int d; enum PyUnicode_Kind kind; a = (PyLongObject *)aa; // 添加打印代码 printf(\"ob_size = %d\\n\", Py_SIZE(a)); for (int index = 0; index ob_digit[index]); } ... } 编译安装后进入 python 解释器输入如下代码 num = 9223372043297226753 print(num) # output >>> ob_size = 3 >>> ob_digit[0] = 1 >>> ob_digit[1] = 6 >>> ob_digit[2] = 8 >>> 9223372043297226753 如下图所示 注:这里的 30 是由 PyLong_SHIFT 决定的,64 位系统中,PyLong_SHIFT 为 30,否则 PyLong_SHIFT 为 15 整数对象的数值操作 可以看到整数对象的数值操作较多,由于篇幅限制无法一一分析,这里只分析整数的部分操作 源文件:Objects/longobject.c // Objects/longobject.c static PyNumberMethods long_as_number = { (binaryfunc)long_add, /*nb_add 加法 */ (binaryfunc)long_sub, /*nb_subtract 减法 */ (binaryfunc)long_mul, /*nb_multiply 乘法 */ long_mod, /*nb_remainder 取余 */ long_divmod, /*nb_divmod */ long_pow, /*nb_power 求幂 */ (unaryfunc)long_neg, /*nb_negative */ (unaryfunc)long_long, /*tp_positive */ (unaryfunc)long_abs, /*tp_absolute 绝对值 */ (inquiry)long_bool, /*tp_bool 求bool值 */ (unaryfunc)long_invert, /*nb_invert 反转 */ long_lshift, /*nb_lshift 逻辑左移 */ (binaryfunc)long_rshift, /*nb_rshift 逻辑右移 */ long_and, /*nb_and 与操作 */ long_xor, /*nb_xor 异或 */ long_or, /*nb_or 或操作 */ long_long, /*nb_int*/ 0, /*nb_reserved*/ long_float, /*nb_float*/ 0, /* nb_inplace_add */ 0, /* nb_inplace_subtract */ 0, /* nb_inplace_multiply */ 0, /* nb_inplace_remainder */ 0, /* nb_inplace_power */ 0, /* nb_inplace_lshift */ 0, /* nb_inplace_rshift */ 0, /* nb_inplace_and */ 0, /* nb_inplace_xor */ 0, /* nb_inplace_or */ long_div, /* nb_floor_divide */ long_true_divide, /* nb_true_divide */ 0, /* nb_inplace_floor_divide */ 0, /* nb_inplace_true_divide */ long_long, /* nb_index */ }; 整数相加 源文件:Objects/longobject.c // Objects/longobject.c static PyObject * long_add(PyLongObject *a, PyLongObject *b) { PyLongObject *z; CHECK_BINOP(a, b); if (Py_ABS(Py_SIZE(a)) 可以看到整数的加法运算函数 long_add 根据 a、b 的 ob_size 又细分为两个函数 (x_add 和 x_sub) 做处理 源文件:Objects/longobject.c // Objects/longobject.c /* Add the absolute values of two integers. */ static PyLongObject * x_add(PyLongObject *a, PyLongObject *b) { Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b)); PyLongObject *z; Py_ssize_t i; digit carry = 0; /* Ensure a is the larger of the two: */ // 确保 a 大于 b if (size_a ob_digit[i] + b->ob_digit[i]; z->ob_digit[i] = carry & PyLong_MASK; carry >>= PyLong_SHIFT; } for (; i ob_digit[i]; z->ob_digit[i] = carry & PyLong_MASK; carry >>= PyLong_SHIFT; } z->ob_digit[i] = carry; return long_normalize(z); } 加法运算函数 x_add 从 ob_digit 数组的低位开始依次按位相加,carry 做进位处理,然后处理 a 对象的高位数字,最后使用 long_normalize 函数调整 ob_size,确保 ob_digit[abs(ob_size)-1]不为零,这与普通四则运算的加法运算相同,只不过进位单元不同而已 源文件:Objects/longobject.c // Objects/longobject.c /* Subtract the absolute values of two integers. */ static PyLongObject * x_sub(PyLongObject *a, PyLongObject *b) { Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b)); PyLongObject *z; Py_ssize_t i; int sign = 1; digit borrow = 0; /* Ensure a is the larger of the two: */ // 确保 a 大于 b if (size_a = 0 && a->ob_digit[i] == b->ob_digit[i]) ; if (i ob_digit[i] ob_digit[i]) { sign = -1; { PyLongObject *temp = a; a = b; b = temp; } } size_a = size_b = i+1; } z = _PyLong_New(size_a); if (z == NULL) return NULL; for (i = 0; i PyLong_SHIFT. */ borrow = a->ob_digit[i] - b->ob_digit[i] - borrow; z->ob_digit[i] = borrow & PyLong_MASK; borrow >>= PyLong_SHIFT; borrow &= 1; /* Keep only one sign bit */ } for (; i ob_digit[i] - borrow; z->ob_digit[i] = borrow & PyLong_MASK; borrow >>= PyLong_SHIFT; borrow &= 1; /* Keep only one sign bit */ } assert(borrow == 0); if (sign 与普通四则运算减法相同,数不够大则向高一位借位, 减法运算函数 x_sub 的示例图如下,注:PyLong_SHIFT 为 30 整数相乘 源文件:Objects/longobject.c // Objects/longobject.c static PyObject * long_mul(PyLongObject *a, PyLongObject *b) { PyLongObject *z; CHECK_BINOP(a, b); /* fast path for single-digit multiplication */ if (Py_ABS(Py_SIZE(a)) k_mul 函数是一种快速乘法 源文件 Karatsuba 的算法主要是用于两个大数的乘法,极大提高了运算效率,相较于普通乘法降低了复杂度,并在其中运用了递归的思想。 基本的原理和做法是将位数很多的两个大数 x 和 y 分成位数较少的数,每个数都是原来 x 和 y 位数的一半。 这样处理之后,简化为做三次乘法,并附带少量的加法操作和移位操作。 具体可以看 wiki Karatsuba 算法的实现 Copyright © FlagGo 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-03-23 19:36:27 "},"objects/list-object/":{"url":"objects/list-object/","title":"Python List 对象","keywords":"","body":"Python List 对象 在Python中的list可以存放任何类型的数据,查看PyListObject可以发现,list实际存放的是PyObject* 指针 PyListObject 源文件:Include/listobject.h // listobject.h typedef struct { PyObject_VAR_HEAD /* Vector of pointers to list elements. list[0] is ob_item[0], etc. */ PyObject **ob_item; /* ob_item contains space for 'allocated' elements. The number * currently in use is ob_size. * Invariants: * 0 示例 lst = [] lst.append(1) 其存储结构如下图 PyListObject对象的一些操作 创建PyListObject PyList_New 对象赋值 PyList_SetItem 获取元素 PyList_GetItem 插入元素 PyList_Insert 追加元素 PyList_Append 移除元素 list_remove 调整list大小 list_resize PyList_New 创建对象 为了避免频繁的申请内存空间,创建PyListObject的时候会先检查缓冲池是否有可用空间 源文件:Objects/listobject.c // listobject.c PyObject * PyList_New(Py_ssize_t size) { PyListObject *op; #ifdef SHOW_ALLOC_COUNT static int initialized = 0; if (!initialized) { Py_AtExit(show_alloc); initialized = 1; } #endif // size 合法性检查 if (size ob_item = NULL; else { op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *)); if (op->ob_item == NULL) { Py_DECREF(op); return PyErr_NoMemory(); } } Py_SIZE(op) = size; op->allocated = size; _PyObject_GC_TRACK(op); return (PyObject *) op; } PyListObject缓冲池默认大小为80 源文件:Include/listobject.c // listobject.c /* Empty list reuse scheme to save calls to malloc and free */ #ifndef PyList_MAXFREELIST #define PyList_MAXFREELIST 80 #endif static PyListObject *free_list[PyList_MAXFREELIST]; static int numfree = 0; PyList_SetItem 元素赋值 源文件:Objects/listobject.c // listobject.c int PyList_SetItem(PyObject *op, Py_ssize_t i, PyObject *newitem) { PyObject **p; if (!PyList_Check(op)) { Py_XDECREF(newitem); PyErr_BadInternalCall(); return -1; } if (i = Py_SIZE(op)) { Py_XDECREF(newitem); PyErr_SetString(PyExc_IndexError, \"list assignment index out of range\"); return -1; } p = ((PyListObject *)op) -> ob_item + i; Py_XSETREF(*p, newitem); return 0; } 元素赋值的示例 lst = [0, 1, 2] lst[0] = 3 # 这里 lst[0] = 3 会调用 PyList_SetItem 函数 PyList_GetItem 获取元素 源文件:Objects/listobject.c // Objects/listobject.c PyObject * PyList_GetItem(PyObject *op, Py_ssize_t i) { if (!PyList_Check(op)) { PyErr_BadInternalCall(); return NULL; } if (i = Py_SIZE(op)) { if (indexerr == NULL) { indexerr = PyUnicode_FromString( \"list index out of range\"); if (indexerr == NULL) return NULL; } PyErr_SetObject(PyExc_IndexError, indexerr); return NULL; } return ((PyListObject *)op) -> ob_item[i]; } 获取元素的示例 lst = [1, 2, 3, 4] print(lst[3]) # lst[3] 实际调用的就是 PyList_GetItem # 根据索引返回对应的元素 PyList_Append 追加元素 PyList_Append 调用 app1 int PyList_Append(PyObject *op, PyObject *newitem) { if (PyList_Check(op) && (newitem != NULL)) return app1((PyListObject *)op, newitem); PyErr_BadInternalCall(); return -1; } 源文件:Objects/listobject.c // Objects/listobject.c static int app1(PyListObject *self, PyObject *v) { Py_ssize_t n = PyList_GET_SIZE(self); assert (v != NULL); if (n == PY_SSIZE_T_MAX) { PyErr_SetString(PyExc_OverflowError, \"cannot add more objects to list\"); return -1; } if (list_resize(self, n+1) 从app1代码可以看出追加元素操作大致流程如下 调用list_resize,将list大小加一 将元素插入list尾部 PyList_Insert 插入元素 PyList_Insert 调用 ins1 int PyList_Insert(PyObject *op, Py_ssize_t where, PyObject *newitem) { if (!PyList_Check(op)) { PyErr_BadInternalCall(); return -1; } return ins1((PyListObject *)op, where, newitem); } 源文件:Objects/listobject.c // Objects/listobject.c static int ins1(PyListObject *self, Py_ssize_t where, PyObject *v) { Py_ssize_t i, n = Py_SIZE(self); PyObject **items; if (v == NULL) { PyErr_BadInternalCall(); return -1; } if (n == PY_SSIZE_T_MAX) { PyErr_SetString(PyExc_OverflowError, \"cannot add more objects to list\"); return -1; } if (list_resize(self, n+1) n) where = n; items = self->ob_item; for (i = n; --i >= where; ) items[i+1] = items[i]; Py_INCREF(v); items[where] = v; return 0; } 从ins1代码可以看出插入元素操作大致流程如下 调用list_resize,将list大小加一 将要插入的位置的元素都往后移一个位置 将元素插入指定位置 list_remove 移除元素 源文件:Objects/listobject.c // listobject.c static PyObject * list_remove(PyListObject *self, PyObject *value) /*[clinic end generated code: output=f087e1951a5e30d1 input=2dc2ba5bb2fb1f82]*/ { Py_ssize_t i; for (i = 0; i ob_item[i], value, Py_EQ); if (cmp > 0) { if (list_ass_slice(self, i, i+1, (PyObject *)NULL) == 0) Py_RETURN_NONE; return NULL; } else if (cmp 移除元素示例 lst = [0, 2, 4, 3] lst.remove(3) \"\"\" lst.remove(3) 会调用 list_remove函数, list_remove函数会遍历列表,使用PyObject_RichCompareBool与目标值进行比较, 相同则调用list_ass_slice进行移除,当遍历完列表还未找到则报错 \"\"\" list_resize 调整list存储空间 随着list元素的增加,list的存储空间可能会不够用,这个时候就需要扩大list的存储空间。 随着list元素的减少,list的存储空间可能存在冗余,这个时候就需要缩小list的存储空间。 函数list_resize就是用于调节list存储空间大小的 源文件:Objects/listobject.c // listobject.c static int list_resize(PyListObject *self, Py_ssize_t newsize) { PyObject **items; size_t new_allocated, num_allocated_bytes; Py_ssize_t allocated = self->allocated; /* Bypass realloc() when a previous overallocation is large enough to accommodate the newsize. If the newsize falls lower than half the allocated size, then proceed with the realloc() to shrink the list. */ if (allocated >= newsize && newsize >= (allocated >> 1)) { assert(self->ob_item != NULL || newsize == 0); Py_SIZE(self) = newsize; return 0; } /* This over-allocates proportional to the list size, making room * for additional growth. The over-allocation is mild, but is * enough to give linear-time amortized behavior over a long * sequence of appends() in the presence of a poorly-performing * system realloc(). * The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ... * Note: new_allocated won't overflow because the largest possible value * is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t. */ new_allocated = (size_t)newsize + (newsize >> 3) + (newsize (size_t)PY_SSIZE_T_MAX / sizeof(PyObject *)) { PyErr_NoMemory(); return -1; } if (newsize == 0) new_allocated = 0; num_allocated_bytes = new_allocated * sizeof(PyObject *); items = (PyObject **)PyMem_Realloc(self->ob_item, num_allocated_bytes); if (items == NULL) { PyErr_NoMemory(); return -1; } self->ob_item = items; Py_SIZE(self) = newsize; self->allocated = new_allocated; return 0; } 当 allocated/2 时,list_resize只会改变 ob_size不会改变allocated。 其他情况则需要调用PyMem_Realloc函数分配新的空间存储列表元素。 列表allocated的增长模式是 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ... 其公式为 new_allocated = (size_t)newsize + (newsize >> 3) + (newsize Copyright © FlagGo 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-03-23 19:36:27 "},"objects/dict-object/":{"url":"objects/dict-object/","title":"Python Dict 对象","keywords":"","body":"Python 字典 Dictionary object implementation using a hash table ,通过描述可知,python 的字典就是实现了一个 hash 表。 Python 字典概述 在 python 的字典中,一个键值对的对应保存就是 PyDictEntry 类型来保存; 源文件:Include/dict-common.h // Objects/dict-common.h typedef struct { /* Cached hash code of me_key. */ Py_hash_t me_hash; PyObject *me_key; PyObject *me_value; /* This field is only meaningful for combined tables */ } PyDictKeyEntry; 其中,me_hash 就是哈希生成的值,me_key 就是对应的 key 值,me_value 就是对应的值。 在 python 中,在一个 PyDictObject 对象的变化过程中,entry 的状态会在不同的状态间转换。基本上在如下四种状态中转换:Unused、Active、Dummy 和 Pending。 Unused:没有插入任何一个获取的 key 与 value,并且在此之前也没有存储任何的 key,value,每一个 entry 在初始化的时候都会处于这种状态,并且 Unused 会被里面切换到 Active 态,当有 key 插入,这就是 entry 初始化的状态。 Active:当 index>=0 时,me_key 不为空并且 me_value 不为空,保存了一个键值对,Active 可以转变为 Dummy 或者 Pending 状态,当一个键被删除的时候,这只会在 me_value 不为空的时候出现。 Dummy:先前保存了一个 Active 的键值对,但是这个键值对被删除了并且一个活跃的键值对还没有填入该位置,Dummy 可以转变为 Active 当删除的时候,Dummy 的位置不能被重新使用,一旦发生碰撞,探针序列就无法知道这对键值对曾是活跃的键值对。 Pending:索引>=0,键!=空,值=空(仅拆分),尚未插入到拆分表中。 字典的两种类型 python 的字典类型中包含了两种,分离字典(split-table dictionaries)与联合字典(combined-table dictonaries)。详细的信息可查看有关 dict 的描述pep-0412。 split-table dictionaries 当被创建的字典是用来保存 object 的__dict__属性时,该字典才会创建为一个 split-table,它们的键表都被缓存在类型属性中,并且允许所有该类型的实例都可以共享该 keys。当出现一个事件将字典的属性值进行改变的时候,个别字典将慢慢的转化成组合表的形式。这就保证了在大部分的应用场景下很高的内存利用效率,并保证了在各个场景下的正确性。当 split-dict 重新改变大小,它会立马改变为一个 combined-table,如果重置大小作为保存实例属性的结果,并且只有一个该 object 的实例,字典会立马再变为一个 split-table。如果从 split-table 中删除一个 key, value,它不会删除 keys tables 中对应的该值,而只是将 values 数值中移除了该 value。 combined-table dictionaries 直接通过 dict 內建函数与{}生成的字典,模块和大部分其他字典都会创建为 combined-table 字典,一个 combined-table 不会改变为一个 split-table 字典,该字典的行为方式与最初的字典的行为方式大致相同。 容器的相关数据结构 字典对象是通过 PyDictObject 来实现数据的,详情如下; 源文件:Include/dictobject.h // Include/dictobject.h typedef struct _dictkeysobject PyDictKeysObject; /* The ma_values pointer is NULL for a combined table * or points to an array of PyObject* for a split table */ typedef struct { PyObject_HEAD /* Number of items in the dictionary */ Py_ssize_t ma_used; // 使用的keys个数 /* Dictionary version: globally unique, value change each time the dictionary is modified */ uint64_t ma_version_tag; PyDictKeysObject *ma_keys; // 如果有则是保存的keys数据 /* If ma_values is NULL, the table is \"combined\": keys and values are stored in ma_keys. If ma_values is not NULL, the table is splitted: keys are stored in ma_keys and values are stored in ma_values */ PyObject **ma_values; // 如果不为空则保存的是values } PyDictObject; 其中,PyDictKeysObject 的定义如下; 源文件:Include/dict-common.h // Objects/dict-common.h /* See dictobject.c for actual layout of DictKeysObject */ struct _dictkeysobject { Py_ssize_t dk_refcnt; // 引用计数 /* Size of the hash table (dk_indices). It must be a power of 2. */ Py_ssize_t dk_size; // hash table 的大小必须是2的倍数 /* Function to lookup in the hash table (dk_indices): - lookdict(): general-purpose, and may return DKIX_ERROR if (and only if) a comparison raises an exception. - lookdict_unicode(): specialized to Unicode string keys, comparison of which can never raise an exception; that function can never return DKIX_ERROR. - lookdict_unicode_nodummy(): similar to lookdict_unicode() but further specialized for Unicode string keys that cannot be the value. - lookdict_split(): Version of lookdict() for split tables. */ dict_lookup_func dk_lookup; // 哈希查找函数 /* Number of usable entries in dk_entries. */ Py_ssize_t dk_usable; // 可用的entry数量 /* Number of used entries in dk_entries. */ Py_ssize_t dk_nentries; // 已经使用的entry数量 /* Actual hash table of dk_size entries. It holds indices in dk_entries, or DKIX_EMPTY(-1) or DKIX_DUMMY(-2). Indices must be: 0 相关数据结构的内存布局为; Python 字典示例 本次示例脚本如下: d = {} d['1']='2' d['1']='e' d.pop('1') 通过 Python 的反汇编工具获取字节码; python -m dis dict_test.py 输出的字节码如下; 2 0 BUILD_MAP 0 2 STORE_NAME 0 (d) 3 4 LOAD_CONST 0 ('2') 6 LOAD_NAME 0 (d) 8 LOAD_CONST 1 ('1') 10 STORE_SUBSCR 4 12 LOAD_CONST 2 ('e') 14 LOAD_NAME 0 (d) 16 LOAD_CONST 1 ('1') 18 STORE_SUBSCR 5 20 LOAD_NAME 0 (d) 22 LOAD_METHOD 1 (pop) 24 LOAD_CONST 1 ('1') 26 CALL_METHOD 1 28 POP_TOP 30 LOAD_CONST 3 (None) 32 RETURN_VALUE 通过字节码指令可知,首先调用了 BUILD_MAP 来创建一个新的字典,接着就对新建的字典 d 进行了赋值操作与更新操作,最后调用了 pop 方法删除一个 key。接下来就详细分析一下相关流程。 字典的初始化流程 通过查找 BUILD_MAP 的虚拟机执行函数; 源文件:Python/ceval.c // Python/ceval.c switch (opcode) { ... TARGET(BUILD_MAP) { Py_ssize_t i; PyObject *map = _PyDict_NewPresized((Py_ssize_t)oparg); // 新建并初始化一个字典 if (map == NULL) goto error; // 如果新建失败则报错 for (i = oparg; i > 0; i--) { // 检查在新建的过程中是否通过参数传值 int err; PyObject *key = PEEK(2*i); PyObject *value = PEEK(2*i - 1); err = PyDict_SetItem(map, key, value); // 找到对应的值并讲该值设置到map中 if (err != 0) { // 检查是否报错 Py_DECREF(map); goto error; // 如果错误就报错处理 } } while (oparg--) { Py_DECREF(POP()); // 弹出栈上输入参数的引用 Py_DECREF(POP()); } PUSH(map); // 讲生成的map压栈 DISPATCH(); // 检查是否需要执行下一条字节码指令 } } 从该函数的执行可知,初始化的函数是从_PyDict_NewPresized 开始,该函数就是生成并初始化一个字典; 源文件:Objects/dictobject.c // Objects/dictobject.c PyObject * _PyDict_NewPresized(Py_ssize_t minused) { const Py_ssize_t max_presize = 128 * 1024; // 字典最大的容量 Py_ssize_t newsize; PyDictKeysObject *new_keys; /* There are no strict guarantee that returned dict can contain minused * items without resize. So we create medium size dict instead of very * large dict or MemoryError. */ if (minused > USABLE_FRACTION(max_presize)) { // 检查传入的数量是否超过最大值 newsize = max_presize; } else { Py_ssize_t minsize = ESTIMATE_SIZE(minused); // 获取最小的值,在新建一个空的字典的时候该值为0 newsize = PyDict_MINSIZE; // 设置字典的最小值 为8 while (newsize 首先,先计算出需要生成的字典的大小,然后再初始化一个 PyDictKeysObject,最后就生成一个 PyDictObject 返回。继续查看 new_keys_object 的执行流程; 源文件:Objects/dictobject.c // Objects/dictobject.c static PyDictKeysObject *new_keys_object(Py_ssize_t size) { PyDictKeysObject *dk; Py_ssize_t es, usable; assert(size >= PyDict_MINSIZE); // 检查size是否大于最小size assert(IS_POWER_OF_2(size)); // 检查是否是2的倍数 usable = USABLE_FRACTION(size); // 检查是否可用 根据经验在1/2和2/3之间效果最好 if (size 4 else if (size 0) { // 是否有缓存,如果有缓存就选择缓存中的dk dk = keys_free_list[--numfreekeys]; } else { dk = PyObject_MALLOC(sizeof(PyDictKeysObject) + es * size + sizeof(PyDictKeyEntry) * usable); // 没有缓存可使用的字典则申请内存生成一个 if (dk == NULL) { PyErr_NoMemory(); return NULL; } } DK_DEBUG_INCREF dk->dk_refcnt = 1; // 设置引用计数 dk->dk_size = size; // 设置大小 dk->dk_usable = usable; // 设置是否可用 dk->dk_lookup = lookdict_unicode_nodummy; // 设置查找函数 dk->dk_nentries = 0; memset(&dk->dk_indices[0], 0xff, es * size); // 将申请的内存置空 memset(DK_ENTRIES(dk), 0, sizeof(PyDictKeyEntry) * usable); return dk; } 主要就是通过传入的 size,检查是否超过设置的大小,检查是否有缓存的字典数据可用,如果没有则申请内存重新生成一个 dk,最后进行申请到的内存讲内容清空。接着就会进行 new_dict 初始化数据; 源文件:Objects/dictobject.c // Objects/dictobject.c /* Consumes a reference to the keys object */ static PyObject * new_dict(PyDictKeysObject *keys, PyObject **values) { PyDictObject *mp; assert(keys != NULL); if (numfree) { // 判断缓冲池是否有 mp = free_list[--numfree]; assert (mp != NULL); assert (Py_TYPE(mp) == &PyDict_Type); _Py_NewReference((PyObject *)mp); // 使用缓冲池对象 } else { mp = PyObject_GC_New(PyDictObject, &PyDict_Type); // 缓冲池没有则申请新的对象并初始化 if (mp == NULL) { DK_DECREF(keys); free_values(values); return NULL; } } mp->ma_keys = keys; mp->ma_values = values; mp->ma_used = 0; // 设置ma_used为0 mp->ma_version_tag = DICT_NEXT_VERSION(); assert(_PyDict_CheckConsistency(mp)); return (PyObject *)mp; } new_dict 就是根据 keys,values 设置到从缓冲池或者新生成一个 dict 对象,最后返回。至此,dict 的创建工作已经完成。 字典的插入与查找 通过字节码的指令 STORE_SUBSCR 可知,该命令就是讲'1'作为 key, '2'作为 value 插入到 d 中,此时查看该执行函数; 源文件:Python/ceval.c // Python/ceval.c switch (opcode) { ... TARGET(STORE_SUBSCR) { PyObject *sub = TOP(); // 第一个值为key PyObject *container = SECOND(); // 该为字典对象 PyObject *v = THIRD(); // 该为value int err; STACKADJ(-3); /* container[sub] = v */ err = PyObject_SetItem(container, sub, v); // 调用该方法设置值 Py_DECREF(v); Py_DECREF(container); Py_DECREF(sub); if (err != 0) goto error; DISPATCH(); } } 此时,从栈中取出相关参数,并将这些值传入 PyObject_SetItem 函数进行处理设置值; 源文件:Objects/abstract.c // Objects/abstract.c int PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value) { PyMappingMethods *m; if (o == NULL || key == NULL || value == NULL) { // 检查是否为空如果任一为空则报错 null_error(); return -1; } m = o->ob_type->tp_as_mapping; // 获取类型的tp_as_mapping方法集 if (m && m->mp_ass_subscript) // 如果有设置该类型 return m->mp_ass_subscript(o, key, value); // 调用该mp_ass_subscript方法 if (o->ob_type->tp_as_sequence) { // 获取作为队列的操作集 if (PyIndex_Check(key)) { // 检查key是否是索引 Py_ssize_t key_value; key_value = PyNumber_AsSsize_t(key, PyExc_IndexError); if (key_value == -1 && PyErr_Occurred()) return -1; return PySequence_SetItem(o, key_value, value); // 调用索引插入 } else if (o->ob_type->tp_as_sequence->sq_ass_item) { type_error(\"sequence index must be \" \"integer, not '%.200s'\", key); return -1; } } type_error(\"'%.200s' object does not support item assignment\", o); // 则该类型对象不支持设置 return -1; } 其中就调用了字典的 tp_as_mapping 的方法集,并调用了该方法集的 mp_ass_subscript 方法;此时我们分析一下,dict 的 tp_as_mapping 的方法集。此时就调用了 tp_as_mapping 的 mp_ass_subscript 方法,此时就是调用 dict 的 dict_ass_sub 方法; 源文件:Objects/dictobject.c // Objects/dictobject.c static int dict_ass_sub(PyDictObject *mp, PyObject *v, PyObject *w) { if (w == NULL) return PyDict_DelItem((PyObject *)mp, v); else return PyDict_SetItem((PyObject *)mp, v, w); } 可知,删除一个 key 就是 PyDict_DelItem,设置一个 key 就是 PyDict_SetItem; 源文件:Objects/dictobject.c // Objects/dictobject.c int PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value) { PyDictObject *mp; Py_hash_t hash; if (!PyDict_Check(op)) { // 检查是否是字典类型 PyErr_BadInternalCall(); return -1; } assert(key); assert(value); mp = (PyDictObject *)op; if (!PyUnicode_CheckExact(key) || (hash = ((PyASCIIObject *) key)->hash) == -1) // 检查传入的key是否hash为-1 { hash = PyObject_Hash(key); // 生成hash调用key对应的tp_hash方法,在本例中传入的是str类型,则调用str类型的tp_hash方法 if (hash == -1) return -1; } /* insertdict() handles any resizing that might be necessary */ return insertdict(mp, key, hash, value); // 生成hash调用key对应的tp_hash方法 } insertdict 方法就是将生成的方法,插入到字典中去; 源文件:Objects/dictobject.c // Objects/dictobject.c static int insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value) { PyObject *old_value; PyDictKeyEntry *ep; Py_INCREF(key); Py_INCREF(value); if (mp->ma_values != NULL && !PyUnicode_CheckExact(key)) { if (insertion_resize(mp) ma_keys->dk_lookup(mp, key, hash, &old_value); // 调用查找方法 if (ix == DKIX_ERROR) goto Fail; assert(PyUnicode_CheckExact(key) || mp->ma_keys->dk_lookup == lookdict); MAINTAIN_TRACKING(mp, key, value); // 检查mp key values是否需要加入垃圾回收 /* When insertion order is different from shared key, we can't share * the key anymore. Convert this instance to combine table. */ if (_PyDict_HasSplitTable(mp) && ((ix >= 0 && old_value == NULL && mp->ma_used != ix) || (ix == DKIX_EMPTY && mp->ma_used != mp->ma_keys->dk_nentries))) { // 检查是否是分离表,如果没查找到旧值并且 if (insertion_resize(mp) ma_keys->dk_usable ma_keys, hash); // 查找一个可用的hash位置 ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries]; // 获取存取的地址 dk_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries); // 设置该值 ep->me_key = key; // 保存key ep->me_hash = hash; // 保存计算得出的hash值 if (mp->ma_values) { // 如果mp的ma_values有值 assert (mp->ma_values[mp->ma_keys->dk_nentries] == NULL); mp->ma_values[mp->ma_keys->dk_nentries] = value; // 设置该key对应的value } else { ep->me_value = value; // 直接讲value设置到entry上面 } mp->ma_used++; // 使用个数加1 mp->ma_version_tag = DICT_NEXT_VERSION(); mp->ma_keys->dk_usable--; // 可用减1 mp->ma_keys->dk_nentries++; assert(mp->ma_keys->dk_usable >= 0); assert(_PyDict_CheckConsistency(mp)); return 0; } if (_PyDict_HasSplitTable(mp)) { // 如果是分离的 mp->ma_values[ix] = value; // 直接设置ma_values对应的ix到values中 if (old_value == NULL) { /* pending state */ assert(ix == mp->ma_used); mp->ma_used++; // 使用加1 } } else { assert(old_value != NULL); DK_ENTRIES(mp->ma_keys)[ix].me_value = value; } mp->ma_version_tag = DICT_NEXT_VERSION(); Py_XDECREF(old_value); /* which **CAN** re-enter (see issue #22653) */ assert(_PyDict_CheckConsistency(mp)); Py_DECREF(key); return 0; Fail: Py_DECREF(value); Py_DECREF(key); return -1; } 首先会调用相关的查找方法,去查找待搜索的值是否已经存在字典中,如果当前字典数据已经满了则会按照增长大小的函数生成一个新的字典,并把旧数据设置到新的字典中,当找到的字典匹配时则返回。 其中 dk_lookup 对应的方法,在初始化之后对应的是 lookdict_unicode_nodummy; 源文件:Objects/dictobject.c // Objects/dictobject.c /* Faster version of lookdict_unicode when it is known that no keys * will be present. */ static Py_ssize_t _Py_HOT_FUNCTION lookdict_unicode_nodummy(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject **value_addr) { assert(mp->ma_values == NULL); /* Make sure this function doesn't have to handle non-unicode keys, including subclasses of str; e.g., one reason to subclass unicodes is to override __eq__, and for speed we don't cater to that here. */ if (!PyUnicode_CheckExact(key)) { // 检查如果不是unicode则直接调用lookdict方法查找 mp->ma_keys->dk_lookup = lookdict; return lookdict(mp, key, hash, value_addr); } PyDictKeyEntry *ep0 = DK_ENTRIES(mp->ma_keys); // 获取keys的首个元素地址 size_t mask = DK_MASK(mp->ma_keys); // 获取大小 size_t perturb = (size_t)hash; size_t i = (size_t)hash & mask; // 获取生成的最终的值 for (;;) { Py_ssize_t ix = dk_get_index(mp->ma_keys, i); // 便利ma_keys key列表 assert (ix != DKIX_DUMMY); // 判断不能为空 if (ix == DKIX_EMPTY) { // 如果为空则证明找到一个可以使用的 *value_addr = NULL; // 讲key对应的value设置为空 return DKIX_EMPTY; // 返回 } PyDictKeyEntry *ep = &ep0[ix]; // 获取该位置元素值 assert(ep->me_key != NULL); assert(PyUnicode_CheckExact(ep->me_key)); if (ep->me_key == key || (ep->me_hash == hash && unicode_eq(ep->me_key, key))) { // 如果key相同 hash值也相同 *value_addr = ep->me_value; // 将该值赋值 return ix; } perturb >>= PERTURB_SHIFT; // 偏移 i = mask & (i*5 + perturb + 1); // 获取下一个位置 } Py_UNREACHABLE(); } 该函数的主要工作就是查找,字典中是否有空余的值,或者如果找到了满足 hash 值与 key 相同的就将 value 设置为找到的值(这也是字典查找的核心逻辑)。至此,字典的插入的大致流程已经分析完毕。 Python 字典的操作测试 现在我们动手观看一下具体的操作实例,首先声明,该例子仅供调试使用,目前调试的字典的 key 与 value 都是 float 类型并且不能 del 或者 pop 其中的 key。操作字典如下所示; d = {20000:2} d[1] = 2 d[3] = 2 首先,讲如下代码插入到 dictobject.c 的 1060 行; // 测试代码 PyObject* key1 = PyLong_FromLong(20000); Py_hash_t hash1 = PyObject_Hash(key1); PyObject* old_value1; Py_ssize_t ix1 = mp->ma_keys->dk_lookup(mp, key1, hash1, &old_value1); if (ix1 == 0){ PyLongObject* give; give = (PyLongObject* )key1; printf(\"found value : %ld\\n\", give->ob_digit[0]); PyDictKeyEntry *ep01 = DK_ENTRIES(mp->ma_keys); int i, count; count = mp->ma_used; int size_count, j; size_count = mp->ma_keys->dk_size; printf(\"%s \", mp->ma_keys->dk_indices); int8_t *indices = (int8_t*)(mp->ma_keys->dk_indices); printf(\"indices index values :\"); for (j=0; jme_key; printf(\"size : %d \", mp->ma_keys->dk_size); printf(\"found value while key : %ld \", give->ob_digit[0]); give = (PyLongObject* )ep01->me_value; printf(\"value : %ld\\n\", give->ob_digit[0]); ep01++; } } 然后编译运行; Python 3.7.3 (default, May 22 2019, 16:17:57) [GCC 7.3.0] on linux Type \"help\", \"copyright\", \"credits\" or \"license\" for more information. >>> d = {20000:2} found value : 20000 indices index values :0 -1 -1 -1 -1 -1 -1 -1 size : 8 found value while key : 20000 value : 2 其中为什么初始化的时候输入 20000,是根据代码找到相关的 key 值,因为字典也被 python 自身实现的结构中引用了多次,所以我们就设置了一个特殊值来跟踪我们想要的字典;当 d 初始化的时候,就输出如上所示内容;我们接下来继续操作; >>> d = {20000:2} found value : 20000 indices index values :0 -1 -1 -1 -1 -1 -1 -1 size : 8 found value while key : 20000 value : 2 >>> d[2] = 3 found value : 20000 indices index values :0 -1 1 -1 -1 -1 -1 -1 size : 8 found value while key : 20000 value : 2 size : 8 found value while key : 2 value : 3 >>> d[3] = 4 found value : 20000 indices index values :0 -1 1 2 -1 -1 -1 -1 size : 8 found value while key : 20000 value : 2 size : 8 found value while key : 2 value : 3 size : 8 found value while key : 3 value : 4 >>> d[5] = 6 found value : 20000 indices index values :0 -1 1 2 -1 3 -1 -1 size : 8 found value while key : 20000 value : 2 size : 8 found value while key : 2 value : 3 size : 8 found value while key : 3 value : 4 size : 8 found value while key : 5 value : 6 >>> d[7] = 8 found value : 20000 indices index values :0 -1 1 2 -1 3 -1 4 size : 8 found value while key : 20000 value : 2 size : 8 found value while key : 2 value : 3 size : 8 found value while key : 3 value : 4 size : 8 found value while key : 5 value : 6 size : 8 found value while key : 7 value : 8 此后我们一直添加值进 d,从输出信息可知,index 就是记录了 PyDictKeyEntry 的索引值,-1 就表示该处未使用。 当我们继续向 d 中添加内容时; >>> d[9] = 10 found value : 20000 indices index values :0 -1 1 2 -1 3 -1 4 -1 5 -1 -1 -1 -1 -1 -1 size : 16 found value while key : 20000 value : 2 size : 16 found value while key : 2 value : 3 size : 16 found value while key : 3 value : 4 size : 16 found value while key : 5 value : 6 size : 16 found value while key : 7 value : 8 size : 16 found value while key : 9 value : 10 >>> d[10] = 11 found value : 20000 indices index values :0 -1 1 2 -1 3 -1 4 -1 5 6 -1 -1 -1 -1 -1 size : 16 found value while key : 20000 value : 2 size : 16 found value while key : 2 value : 3 size : 16 found value while key : 3 value : 4 size : 16 found value while key : 5 value : 6 size : 16 found value while key : 7 value : 8 size : 16 found value while key : 9 value : 10 size : 16 found value while key : 10 value : 11 从输出内容可知,字典的大小随之改变了,这也说明了 python 字典的最佳大小容量限定在 1/2 到 2/3 之间,如果超过这个阈值则字典就会自动扩容,扩容的策略大家可详细查看源码。 Copyright © FlagGo 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-03-23 19:36:27 "},"objects/set-object/":{"url":"objects/set-object/","title":"Python Set 对象","keywords":"","body":"python 集合 set 是无序且不重复的集合,是可变的,通常用来从列表中删除重复项以及计算数学运算,如交集、并集、差分和对称差分等集合操作。set 支持 x in set, len(set),和 for x in set。作为一个无序的集合,set 不记录元素位置或者插入点。因此,sets 不支持 indexing, 或其它类序列的操作。 python 集合概述 在 set 中,对应的 set 的值的存储是通过结构 setentry 来保存数据值的; 源文件:include/setobject.h typedef struct { PyObject *key; Py_hash_t hash; /* Cached hash code of the key */ } setentry; key 就是保存的数据,hash 就是保存的数据的 hash,便于查找,set 也是基于 hash 表来实现。对应的 setentry 所对应的 set 的数据结构如下; 源文件:include/setobject.h typedef struct { PyObject_HEAD Py_ssize_t fill; /* Number active and dummy entries*/ // 包括已经使用的entry与空entry值的总和 Py_ssize_t used; /* Number active entries */ // 已经使用可用的总量 /* The table contains mask + 1 slots, and that's a power of 2. * We store the mask instead of the size because the mask is more * frequently needed. */ Py_ssize_t mask; // 与hash求和的mask /* The table points to a fixed-size smalltable for small tables * or to additional malloc'ed memory for bigger tables. * The table pointer is never NULL which saves us from repeated * runtime null-tests. */ setentry *table; // 保存数据的数组数组指针 Py_hash_t hash; /* Only used by frozenset objects */ Py_ssize_t finger; /* Search finger for pop() */ setentry smalltable[PySet_MINSIZE]; // 保存数据的数组 默认初始化为8个元素,通过table指向 PyObject *weakreflist; /* List of weak references */ } PySetObject; 一个 set 就对应一个 PySetObject 类型数据,set 会根据保存的元素自动调整大小。相关的内存布局如下; python 集合(set)示例 示例脚本如下: set_a = {1,2} set_a.add(3) set_a.add(4) set_a.remove(1) set_a.update({3,}) set_a.union({1,5}) 通过 python 反汇编获取该脚本的字节码; python -m dis set_test.py 输出的字节码如下所示; 1 0 LOAD_CONST 0 (1) 3 LOAD_CONST 1 (2) 6 BUILD_SET 2 9 STORE_NAME 0 (set_a) 2 12 LOAD_NAME 0 (set_a) 15 LOAD_ATTR 1 (add) 18 LOAD_CONST 2 (3) 21 CALL_FUNCTION 1 24 POP_TOP 3 25 LOAD_NAME 0 (set_a) 28 LOAD_ATTR 1 (add) 31 LOAD_CONST 3 (4) 34 CALL_FUNCTION 1 37 POP_TOP 4 38 LOAD_NAME 0 (set_a) 41 LOAD_ATTR 2 (remove) 44 LOAD_CONST 0 (1) 47 CALL_FUNCTION 1 50 POP_TOP 5 51 LOAD_NAME 0 (set_a) 54 LOAD_ATTR 3 (update) 57 LOAD_CONST 2 (3) 60 BUILD_SET 1 63 CALL_FUNCTION 1 66 POP_TOP 6 67 LOAD_NAME 0 (set_a) 70 LOAD_ATTR 4 (union) 73 LOAD_CONST 0 (1) 76 LOAD_CONST 4 (5) 79 BUILD_SET 2 82 CALL_FUNCTION 1 85 POP_TOP 86 LOAD_CONST 5 (None) 89 RETURN_VALUE 通过该字节码指令可知,创建 set 调用了 BUILD_SET 指令,初始化完成之后,就调用 set 的 add 方法添加元素,调用 remove 删除元素,调用 update 来更新集合,通过 union 来合并集合。接下来就详细分析一下相关的操作流程。 set 的创建与初始化 查找 BUILD_SET 的虚拟机执行函数如下; 源文件:Python/ceval.c // Python/ceval.c TARGET(BUILD_SET) { PyObject *set = PySet_New(NULL); // 新建并初始化一个set int err = 0; int i; if (set == NULL) goto error; for (i = oparg; i > 0; i--) { // 将传入初始化的参数传入 PyObject *item = PEEK(i); if (err == 0) err = PySet_Add(set, item); // 并依次对set进行添加操作 Py_DECREF(item); } STACKADJ(-oparg); // 移动弹栈 if (err != 0) { Py_DECREF(set); goto error; } PUSH(set); // 讲set压栈 DISPATCH(); // 执行下一条指令 } 此时继续查看 PySet_New 函数的执行流程; 源文件:Objects/setobject.c PyObject * PySet_New(PyObject *iterable) { return make_new_set(&PySet_Type, iterable); } ... static PyObject * make_new_set(PyTypeObject *type, PyObject *iterable) { PySetObject *so; so = (PySetObject *)type->tp_alloc(type, 0); // 申请该元素的内存 if (so == NULL) // 内存申请失败则返回为空 return NULL; so->fill = 0; // 初始化的时候都为0 so->used = 0; so->mask = PySet_MINSIZE - 1; // PySet_MINSIZE默认我8,mask为7 so->table = so->smalltable; // 将保存数据的头指针指向table so->hash = -1; // 设置hash值为-1 so->finger = 0; so->weakreflist = NULL; if (iterable != NULL) { // 如果有迭代器 if (set_update_internal(so, iterable)) { // 将内容更新到so中 Py_DECREF(so); return NULL; } } return (PyObject *)so; // 返回初始化完成的set } 从 PySet_New 的执行流程可知,字典的初始化过程就是初始化相关数据结构。 set 的插入 在本例的初始化过程中,由于传入了初始值 1,2,所以会在执行字节码指令的时候,执行 PySet_Add,该函数的本质与 set_a.add(3)本质都调用了更底层 set_add_key 函数; 源文件:Objects/setobject.c int PySet_Add(PyObject *anyset, PyObject *key) { if (!PySet_Check(anyset) && (!PyFrozenSet_Check(anyset) || Py_REFCNT(anyset) != 1)) { PyErr_BadInternalCall(); return -1; } return set_add_key((PySetObject *)anyset, key); // 向字典中添加key; } 继续查看 set_add_key 函数的执行过程; 源文件:Objects/setobject.c static int set_add_key(PySetObject *so, PyObject *key) { Py_hash_t hash; if (!PyUnicode_CheckExact(key) || (hash = ((PyASCIIObject *) key)->hash) == -1) { hash = PyObject_Hash(key); // 获取传入值的hash值 if (hash == -1) // 如果不能hash则返回-1 return -1; } return set_add_entry(so, key, hash); // 计算完成后添加值 } 该函数主要就是检查传入的 key 是否能够被 hash,如果能够被 hash 则直接返回,如果能被 hash 则继续调用 set_add_entry 函数将值加入到 set 中; 源文件:Objects/setobject.c static int set_add_entry(PySetObject *so, PyObject *key, Py_hash_t hash) { setentry *table; setentry *freeslot; setentry *entry; size_t perturb; size_t mask; size_t i; /* Unsigned for defined overflow behavior */ size_t j; int cmp; /* Pre-increment is necessary to prevent arbitrary code in the rich comparison from deallocating the key just before the insertion. */ Py_INCREF(key); // 提高key的引用计数 restart: mask = so->mask; // 获取so->mask i = (size_t)hash & mask; // 通过传入的hash与mask求索引下标 entry = &so->table[i]; // 获取索引对应的值 if (entry->key == NULL) // 如果获取索引的值没有被使用则直接跳转到found_unused处执行 goto found_unused; freeslot = NULL; perturb = hash; // perturb设置为当前hash值 while (1) { if (entry->hash == hash) { // 如果当前hash值相等 PyObject *startkey = entry->key; // 获取当前key /* startkey cannot be a dummy because the dummy hash field is -1 */ assert(startkey != dummy); // 检查key是否为dummy if (startkey == key) // 如果找到的值与传入需要设置的值相同则跳转到found_active处执行 goto found_active; if (PyUnicode_CheckExact(startkey) && PyUnicode_CheckExact(key) && _PyUnicode_EQ(startkey, key)) // 如果是unicode,通过类型转换检查两个key的内容是否相同,如果不相同则跳转到found_active处 goto found_active; table = so->table; // 如果没有找到,则获取当前table的头部节点 Py_INCREF(startkey); cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); // 如果是其他类型的对象则调用比较方法去比较两个key是否相同 Py_DECREF(startkey); if (cmp > 0) /* likely */ // 如果找到则跳转到found_active goto found_active; if (cmp table || entry->key != startkey) // 如果set改变了则重新开始查找 goto restart; mask = so->mask; /* help avoid a register spill */ } else if (entry->hash == -1) freeslot = entry; // 如果不能hash 则设置freeslot if (i + LINEAR_PROBES hash == 0 && entry->key == NULL) // 如果找到当前hash为空或者key为空的则跳转到found_unused_or_dummy处执行 goto found_unused_or_dummy; if (entry->hash == hash) { // 如果找到的hash值相同 PyObject *startkey = entry->key; // 获取该值 assert(startkey != dummy); // 检查是否为dummy if (startkey == key) // 如果key相同则跳转到found_active处执行 goto found_active; if (PyUnicode_CheckExact(startkey) && PyUnicode_CheckExact(key) && _PyUnicode_EQ(startkey, key)) // 检查是否为unicode,并比较如果不相同则跳转到found_active goto found_active; table = so->table; // 调用key本身的方法比较 Py_INCREF(startkey); cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); Py_DECREF(startkey); if (cmp > 0) goto found_active; if (cmp table || entry->key != startkey) goto restart; mask = so->mask; } else if (entry->hash == -1) freeslot = entry; } } perturb >>= PERTURB_SHIFT; // 如果没有找到则获取下一个索引值 i = (i * 5 + 1 + perturb) & mask; // 右移5位 加上 索引值*5 加1与mask求余获取下一个索引值 entry = &so->table[i]; // 获取下一个元素 if (entry->key == NULL) // 如果找到为空则直接跳转到found_unused_or_dummy处 goto found_unused_or_dummy; } found_unused_or_dummy: if (freeslot == NULL) // 检查freeslot是否为空如果为空则跳转到found_unused处执行即找到了dummy位置 goto found_unused; so->used++; // 使用数加1 freeslot->key = key; // 设置key与hash值 freeslot->hash = hash; return 0; found_unused: so->fill++; // 使用总数加1 so->used++; // 使用总数加1 entry->key = key; // 设置key与hash值 entry->hash = hash; if ((size_t)so->fill*5 used>50000 ? so->used*2 : so->used*4); // 如果已使用的总数大于3/5则重新调整table,如果set使用的总数超过了50000则扩展为以前的2倍否则就是四倍 found_active: Py_DECREF(key); // 如果找到了该值 则什么也不做 return 0; comparison_error: Py_DECREF(key); // 如果比较失败则返回-1 return -1; } 此时基本的流程就是通过传入的 hash 值,如果计算出的索引值,没有值,则直接将该值存入对应的 entry 中,如果相同则不插入,如果索引对应的值且值不同,则遍历从该索引往后9个位置的值,依次找到有空余位置的值,并将该值设置进去。如果设置该值之后使用的数量占总的申请数量超过了 3/5 则重新扩充 set,扩充的原则就是如果当前的 set->used>50000 就进行两倍扩充否则就进行四倍扩充。 插入的概述如下,默认 s 初始化为空; s.add(1) // index = 1 & 7 = 1 s.add(2) // index = 2 & 7 = 2 s.add(7) // index = 9 & 7 = 1 大致的 set 的插入过程执行完毕。 set 的删除 set 的删除操作主要集中在 set_remove()函数上,如下示例; 源文件:Objects/setobject.c static PyObject * set_remove(PySetObject *so, PyObject *key) { PyObject *tmpkey; int rv; rv = set_discard_key(so, key); // 将该key设置为dummy if (rv 此时就会调用 set_discard_key 方法来讲对应的 entry 设置为 dummy;set_discard_key 方法如下; 源文件:Objects/setobject.c static int set_discard_key(PySetObject *so, PyObject *key) { Py_hash_t hash; if (!PyUnicode_CheckExact(key) || (hash = ((PyASCIIObject *) key)->hash) == -1) { hash = PyObject_Hash(key); // 检查是否可用hash如果可用则调用set_discard_entry方法 if (hash == -1) return -1; } return set_discard_entry(so, key, hash); } 该函数主要就是做了检查 key 是否可用 hash 的检查,此时如果可用 hash 则调用 set_discard_entry 方法; 源文件:Objects/setobject.c static int set_discard_entry(PySetObject *so, PyObject *key, Py_hash_t hash) { setentry *entry; PyObject *old_key; entry = set_lookkey(so, key, hash); // 查找该值 set_lookkey该方法与插入的逻辑类似大家可自行查看 if (entry == NULL) // 如果没有找到则返回-1 return -1; if (entry->key == NULL) return DISCARD_NOTFOUND; // 找到entry而key为空则返回notfound old_key = entry->key; // 找到正常值则讲该值对应的entry设置为dummy entry->key = dummy; entry->hash = -1; // hash值为-1 so->used--; // 使用数量减1 但是fill数量未变 Py_DECREF(old_key); // 减少该对象引用 return DISCARD_FOUND; // 返回返现 } 此时就是查找该值,如果找到该值并将该值设置为 dummy,并且将 used 值减1,此处没有减去 fill 的数量,从此处可知,fill 包括所有曾经申请过的数量。 set 的 resize set 的 resize 主要依靠 set_table_reseize 函数来实现; 源文件:Objects/setobject.c static int set_table_resize(PySetObject *so, Py_ssize_t minused) { setentry *oldtable, *newtable, *entry; Py_ssize_t oldmask = so->mask; // 设置旧的mask size_t newmask; int is_oldtable_malloced; setentry small_copy[PySet_MINSIZE]; // 最小的拷贝数组 assert(minused >= 0); /* Find the smallest table size > minused. */ /* XXX speed-up with intrinsics */ size_t newsize = PySet_MINSIZE; while (newsize table; // 先获取旧的table assert(oldtable != NULL); is_oldtable_malloced = oldtable != so->smalltable; if (newsize == PySet_MINSIZE) { // 如果获取的新大小与PySet_MINSIZE的大小相同 /* A large table is shrinking, or we can't get any smaller. */ newtable = so->smalltable; // 获取新table的地址 if (newtable == oldtable) { // 如果相同 if (so->fill == so->used) { // 如果使用的相同则什么都不做 /* No dummies, so no point doing anything. */ return 0; } /* We're not going to resize it, but rebuild the table anyway to purge old dummy entries. Subtle: This is *necessary* if fill==size, as set_lookkey needs at least one virgin slot to terminate failing searches. If fill fill > so->used); memcpy(small_copy, oldtable, sizeof(small_copy)); // 将数据拷贝到set_lookkey中 oldtable = small_copy; } } else { newtable = PyMem_NEW(setentry, newsize); // 新申请内存 if (newtable == NULL) { // 如果为空则申请内存失败报错 PyErr_NoMemory(); return -1; } } /* Make the set empty, using the new table. */ assert(newtable != oldtable); // 检查新申请的与就table不同 memset(newtable, 0, sizeof(setentry) * newsize); // 新申请的内存置空 so->mask = newsize - 1; // 设置新的size so->table = newtable; // 重置table指向新table /* Copy the data over; this is refcount-neutral for active entries; dummy entries aren't copied over, of course */ newmask = (size_t)so->mask; // 获取新的mask if (so->fill == so->used) { // 如果使用的与曾经使用的数量相同 for (entry = oldtable; entry key != NULL) { set_insert_clean(newtable, newmask, entry->key, entry->hash); // 如果值不为空则插入到新的table中 } } } else { so->fill = so->used; // 如果不相同则重置fill为used的值 for (entry = oldtable; entry key != NULL && entry->key != dummy) { // 检查如果不为dummy并且key不为空的情况下 set_insert_clean(newtable, newmask, entry->key, entry->hash); // 重新插入该列表该值 } } } if (is_oldtable_malloced) // 如果两个表相同则删除旧table PyMem_DEL(oldtable); return 0; // 返回0 } 主要是检查是否 table 相同并且需要重新 resize 的值,然后判断是否 fill 与 used 相同,如果相同则全部插入,如果不同,则遍历旧 table 讲不为空并且不为 dummy 的值插入到新表中; 源文件:Objects/setobject.c static void set_insert_clean(setentry *table, size_t mask, PyObject *key, Py_hash_t hash) { setentry *entry; size_t perturb = hash; size_t i = (size_t)hash & mask; // 计算索引 size_t j; while (1) { entry = &table[i]; // 获取当前entry if (entry->key == NULL) // 如果为空则跳转值found_null设置key与hash goto found_null; if (i + LINEAR_PROBES key == NULL) // 如果为空则跳转到found_null goto found_null; } } perturb >>= PERTURB_SHIFT; // 计算下一个索引值继续寻找 i = (i * 5 + 1 + perturb) & mask; } found_null: entry->key = key; entry->hash = hash; } set 的 resize 的操作基本如上所述。 Copyright © FlagGo 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-03-23 19:36:27 "},"objects/simple-interpreter/":{"url":"objects/simple-interpreter/","title":"实现简版 Python","keywords":"","body":"Python 字符串 对象 Copyright © FlagGo 2019 all right reserved,powered by Gitbook该文件修订时间: 2020-03-23 19:36:27 "}}