├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── SUMMARY.md ├── book.json ├── objects ├── dict-object │ ├── dict-mem.png │ └── index.md ├── list-object │ ├── PyListStructure.png │ └── index.md ├── long-object │ ├── index.md │ ├── long-storage.png │ ├── long-x-add.png │ └── long-x-sub.png ├── object │ ├── PyObject.jpg │ ├── PyVarObject.jpg │ ├── index.md │ ├── object-category.jpg │ └── object-runtime-relation.jpg ├── set-object │ ├── index.md │ ├── set-insert-nine.png │ ├── set-insert-one.png │ ├── set-insert-two.png │ └── set.png ├── simple-interpreter │ └── index.md └── str-object │ └── index.md ├── package-lock.json ├── package.json └── preface ├── code-organization └── index.md ├── modify-code └── index.md ├── unix-linux-build └── index.md └── windows-build ├── build-files.png ├── index.md ├── vs2017-build.png ├── vs2017-configure.png ├── vs2017-installation.png └── vs2017-properties.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf 17 | 18 | # mac 19 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "10.6" 5 | 6 | # 缓存依赖 7 | cache: 8 | directories: 9 | - $HOME/.npm 10 | 11 | before_install: 12 | - export TZ='Asia/Shanghai' # 更改时区 13 | 14 | # 依赖安装 15 | install: 16 | - npm install gitbook-cli -g 17 | # 安装 gitbook 插件 18 | - gitbook install 19 | # npm -g 是全局安装, 下面的npm install是安装在项目的node_modules中,这两种构建不冲突 20 | - npm install 21 | 22 | # 构建脚本 23 | script: 24 | - gitbook build 25 | # 不冲突 26 | - npm run build 27 | 28 | # 分支白名单 29 | branches: 30 | only: 31 | - master # 只对 master 分支进行构建 32 | 33 | # GitHub Pages 部署 34 | deploy: 35 | provider: pages 36 | skip_cleanup: true 37 | # 在项目仪表盘的 Settings -> Environment Variables 中配置 38 | github_token: $GITHUB_TOKEN 39 | # 将 build 目录下的内容推送到默认的 gh-pages 分支上,并不会连带 build 目录一起 40 | local_dir: _book 41 | on: 42 | branch: master 43 | 44 | # 通知 45 | notifications: 46 | email: 47 | recipients: 48 | - wangbinxin001@126.com 49 | on_failure: always -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 本作品采用知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。 2 | 要查看该许可协议,可访问 http://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | 或者写信到 Creative Commons, PO Box 1866, Mountain View, CA 94042, USA。 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help: 2 | @echo "\033[32minit\033[0m" 3 | @echo " 初始化GitBook" 4 | @echo "\033[32mrun\033[0m" 5 | @echo " 运行GitBook服务器" 6 | @echo "\033[32mbuild\033[0m" 7 | @echo " 构建GitBook静态页面" 8 | 9 | init: 10 | sudo npm i -g gitbook-cli 11 | gitbook install 12 | 13 | run: 14 | gitbook serve 15 | 16 | build: 17 | gitbook build 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | 本项目致力于对 Python 3.7 的源码分析,深度参考陈儒大大的《Python 源码剖析》,编写 Python 3 的版本。 4 | 5 | 希望各位 Python 爱好者能参与其中,一起探索 Python 魔法背后的奥秘! 6 | 7 | # 使用 8 | 9 | 您可以直接访问 [在线版](https://flaggo.github.io/python3-source-code-analysis/),或者根据以下步骤访问本地版。 10 | 11 | ## 前置条件 12 | 13 | 您的系统上需要安装好 node (会自带npm)。 14 | 15 | ## 使用 make 或者使用 npm 命令去构建 16 | 17 | ### 使用 make 命令的方式构建: 18 | 19 | 若您可使用 make 命令,简单执行如下命令进行初始化: 20 | 21 | ```console 22 | make init 23 | ``` 24 | 25 | 执行如下命令运行服务端: 26 | 27 | ```console 28 | make run 29 | ``` 30 | 31 | ### 使用 npm 命令的方式构建: 32 | 33 | 若您不能使用 make 命令,或想直接使用 npm 命令,执行如下命令进行初始化: 34 | 35 | 安装项目依赖: 36 | 37 | ```console 38 | npm install 39 | ``` 40 | 41 | 执行如下命令运行服务端: 42 | 43 | ```console 44 | npm run serve 45 | ``` 46 | 47 | ## 访问 48 | 49 | 直接访问 http://localhost:4000 即可查看本书内容。 50 | 51 | # Roadmap 52 | 53 | 大体按照《Python 源码剖析》中的目录结构进行编写。依次介绍 Python 源码基本信息、内建对象和虚拟机。 54 | 55 | - [x] 章节 56 | - [x] 序章 57 | - [x] 前言 58 | - [x] Python 源代码的组织 59 | - [x] Windows 环境下编译 Python 60 | - [x] UNIX/Linux 环境下编译 Python 61 | - [x] 修改 Python 源码 62 | - [ ] Python 内建对象 63 | - [x] Python 对象初探 64 | - [x] Python 整数对象 65 | - [ ] Python 字符串 对象 66 | - [x] Python List 对象 67 | - [x] Python Dict 对象 68 | - [x] Python Set 对象 69 | - [ ] 实现简版 Python 70 | - [ ] Python 虚拟机 71 | - [ ] Python 编译结果 72 | - [ ] Python 虚拟机框架 73 | - [ ] 虚拟机一般表达式 74 | - [ ] Python 虚拟机控制流 75 | - [ ] Python 虚拟机函数机制 76 | - [ ] Python 运行环境初始化 77 | - [ ] Python 模块加载机制 78 | - [ ] Python 多线程机制 79 | - [ ] Python 内存管理机制 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # 大纲 2 | 3 | ## 第 1 部分:序章 4 | 5 | - [前言](README.md) 6 | - [Python 源代码的组织](preface/code-organization/index.md) 7 | - [Windows 环境下编译 Python](preface/windows-build/index.md) 8 | - [UNIX/Linux 环境下编译 Python](preface/unix-linux-build/index.md) 9 | - [修改 Python 源码](preface/modify-code/index.md) 10 | 11 | ## 第 2 部分:Python 内建对象 12 | 13 | - [Python 对象初探](objects/object/index.md) 14 | - [Python 整数对象](objects/long-object/index.md) 15 | - [Python 字符串 对象](objects/string-object/index.md) 16 | - [Python List 对象](objects/list-object/index.md) 17 | - [Python Dict 对象](objects/dict-object/index.md) 18 | - [Python Set 对象](objects/set-object/index.md) 19 | - [实现简版 Python](objects/simple-interpreter/index.md) 20 | 21 | ## 第 3 部分:Python 虚拟机 22 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Python 3 源码分析", 3 | "description": "致力于分析Python 3.7.0 的源码实现", 4 | "author": "Prodesire", 5 | "output.name": "site", 6 | "language": "zh-hans", 7 | "gitbook": "3.2.3", 8 | "root": ".", 9 | "structure": { 10 | "readme": "README.md" 11 | }, 12 | "links": { 13 | "sidebar": { 14 | "主页": "https://github.com/flaggo/python3-source-code-analysis" 15 | } 16 | }, 17 | "plugins": [ 18 | "-search", 19 | "search-plus@^0.0.11", 20 | "-sharing", 21 | "sharing-plus", 22 | "github@^2.0.0", 23 | "github-buttons@2.1.0", 24 | "edit-link@^2.0.2", 25 | "splitter", 26 | "tbfed-pagefooter", 27 | "prism", 28 | "page-toc-button", 29 | "back-to-top-button" 30 | ], 31 | 32 | "pluginsConfig": { 33 | "theme-default": { 34 | "showLevel": true 35 | }, 36 | "github": { 37 | "url": "https://github.com/flaggo/python3-source-code-analysis" 38 | }, 39 | "github-buttons": { 40 | "repo": "flaggo/python3-source-code-analysis", 41 | "types": ["star"], 42 | "size": "small" 43 | }, 44 | "sharing": { 45 | "weibo": true, 46 | "douban": true, 47 | "linkedin": true, 48 | "facebook": true, 49 | "google": true, 50 | "twitter": true, 51 | "all": ["weibo", "douban", "linkedin", "facebook", "google", "twitter"] 52 | }, 53 | "tbfed-pagefooter": { 54 | "copyright": "Copyright © FlagGo 2019", 55 | "modify_label": "该文件修订时间:", 56 | "modify_format": "YYYY-MM-DD HH:mm:ss" 57 | }, 58 | "edit-link": { 59 | "base": "https://github.com/flaggo/python3-source-code-analysis/edit/master", 60 | "label": "编辑此页面" 61 | }, 62 | "anchor-navigation-ex": { 63 | "isRewritePageTitle": false, 64 | "tocLevel1Icon": "fa fa-hand-o-right", 65 | "tocLevel2Icon": "fa fa-hand-o-right", 66 | "tocLevel3Icon": "fa fa-hand-o-right" 67 | }, 68 | "prism": { 69 | "lang": { 70 | "console": "bash", 71 | "shell": "bash" 72 | }, 73 | "css": ["prismjs/themes/prism-okaidia.css"] 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /objects/dict-object/dict-mem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/dict-object/dict-mem.png -------------------------------------------------------------------------------- /objects/dict-object/index.md: -------------------------------------------------------------------------------- 1 | # Python 字典 2 | 3 | Dictionary object implementation using a hash table ,通过描述可知,python 的字典就是实现了一个 hash 表。 4 | 5 | ## Python 字典概述 6 | 7 | 在 python 的字典中,一个键值对的对应保存就是 PyDictEntry 类型来保存; 8 | 9 | `源文件:`[Include/dict-common.h](https://github.com/python/cpython/blob/v3.7.0/Objects/dict-common.h#L1) 10 | 11 | ```c 12 | // Objects/dict-common.h 13 | typedef struct { 14 | /* Cached hash code of me_key. */ 15 | Py_hash_t me_hash; 16 | PyObject *me_key; 17 | PyObject *me_value; /* This field is only meaningful for combined tables */ 18 | } PyDictKeyEntry;  19 | ``` 20 | 21 | 其中,me_hash 就是哈希生成的值,me_key 就是对应的 key 值,me_value 就是对应的值。 22 | 在 python 中,在一个 PyDictObject 对象的变化过程中,entry 的状态会在不同的状态间转换。基本上在如下四种状态中转换:Unused、Active、Dummy 和 Pending。 23 | 24 | 1. Unused:没有插入任何一个获取的 key 与 value,并且在此之前也没有存储任何的 key,value,每一个 entry 在初始化的时候都会处于这种状态,并且 Unused 会被里面切换到 Active 态,当有 key 插入,这就是 entry 初始化的状态。 25 | 2. Active:当 index>=0 时,me_key 不为空并且 me_value 不为空,保存了一个键值对,Active 可以转变为 Dummy 或者 Pending 状态,当一个键被删除的时候,这只会在 me_value 不为空的时候出现。 26 | 3. Dummy:先前保存了一个 Active 的键值对,但是这个键值对被删除了并且一个活跃的键值对还没有填入该位置,Dummy 可以转变为 Active 当删除的时候,Dummy 的位置不能被重新使用,一旦发生碰撞,探针序列就无法知道这对键值对曾是活跃的键值对。 27 | 4. Pending:索引>=0,键!=空,值=空(仅拆分),尚未插入到拆分表中。 28 | 29 | ## 字典的两种类型 30 | 31 | python 的字典类型中包含了两种,分离字典(split-table dictionaries)与联合字典(combined-table dictonaries)。详细的信息可查看有关 dict 的描述[pep-0412](https://www.python.org/dev/peps/pep-0412/)。 32 | 33 | ### split-table dictionaries 34 | 35 | 当被创建的字典是用来保存 object 的\_\_dict\_\_属性时,该字典才会创建为一个 split-table,它们的键表都被缓存在类型属性中,并且允许所有该类型的实例都可以共享该 keys。当出现一个事件将字典的属性值进行改变的时候,个别字典将慢慢的转化成组合表的形式。这就保证了在大部分的应用场景下很高的内存利用效率,并保证了在各个场景下的正确性。当 split-dict 重新改变大小,它会立马改变为一个 combined-table,如果重置大小作为保存实例属性的结果,并且只有一个该 object 的实例,字典会立马再变为一个 split-table。如果从 split-table 中删除一个 key, value,它不会删除 keys tables 中对应的该值,而只是将 values 数值中移除了该 value。 36 | 37 | ### combined-table dictionaries 38 | 39 | 直接通过 dict 內建函数与{}生成的字典,模块和大部分其他字典都会创建为 combined-table 字典,一个 combined-table 不会改变为一个 split-table 字典,该字典的行为方式与最初的字典的行为方式大致相同。 40 | 41 | ## 容器的相关数据结构 42 | 43 | 字典对象是通过 PyDictObject 来实现数据的,详情如下; 44 | 45 | `源文件:`[Include/dictobject.h](https://github.com/python/cpython/blob/v3.7.0/Include/dictobject.h#L17) 46 | 47 | ```c 48 | // Include/dictobject.h 49 | typedef struct _dictkeysobject PyDictKeysObject; 50 | 51 | /* The ma_values pointer is NULL for a combined table 52 | * or points to an array of PyObject* for a split table 53 | */ 54 | typedef struct { 55 | PyObject_HEAD 56 | 57 | /* Number of items in the dictionary */ 58 | Py_ssize_t ma_used;  // 使用的keys个数 59 | 60 | /* Dictionary version: globally unique, value change each time 61 | the dictionary is modified */ 62 | uint64_t ma_version_tag; 63 | 64 | PyDictKeysObject *ma_keys;     // 如果有则是保存的keys数据 65 | 66 | /* If ma_values is NULL, the table is "combined": keys and values 67 | are stored in ma_keys. 68 | 69 | If ma_values is not NULL, the table is splitted: 70 | keys are stored in ma_keys and values are stored in ma_values */ 71 | PyObject **ma_values;  // 如果不为空则保存的是values 72 | } PyDictObject; 73 | ``` 74 | 75 | 其中,PyDictKeysObject 的定义如下; 76 | 77 | `源文件:`[Include/dict-common.h](https://github.com/python/cpython/blob/v3.7.0/Objects/dict-common.h#L20) 78 | 79 | ```c 80 | // Objects/dict-common.h 81 | /* See dictobject.c for actual layout of DictKeysObject */ 82 | struct _dictkeysobject { 83 | Py_ssize_t dk_refcnt;                  // 引用计数 84 | 85 | /* Size of the hash table (dk_indices). It must be a power of 2. */ 86 | Py_ssize_t dk_size;                   // hash table 的大小必须是2的倍数 87 | 88 | /* Function to lookup in the hash table (dk_indices): 89 | 90 | - lookdict(): general-purpose, and may return DKIX_ERROR if (and 91 | only if) a comparison raises an exception. 92 | 93 | - lookdict_unicode(): specialized to Unicode string keys, comparison of 94 | which can never raise an exception; that function can never return 95 | DKIX_ERROR. 96 | 97 | - lookdict_unicode_nodummy(): similar to lookdict_unicode() but further 98 | specialized for Unicode string keys that cannot be the value. 99 | 100 | - lookdict_split(): Version of lookdict() for split tables. */ 101 | dict_lookup_func dk_lookup; // 哈希查找函数 102 | 103 | /* Number of usable entries in dk_entries. */ 104 | Py_ssize_t dk_usable; // 可用的entry数量 105 | 106 | /* Number of used entries in dk_entries. */  107 | Py_ssize_t dk_nentries;          // 已经使用的entry数量 108 | 109 | /* Actual hash table of dk_size entries. It holds indices in dk_entries, 110 | or DKIX_EMPTY(-1) or DKIX_DUMMY(-2). 111 | 112 | Indices must be: 0 <= indice < USABLE_FRACTION(dk_size). 113 | 114 | The size in bytes of an indice depends on dk_size: 115 | 116 | - 1 byte if dk_size <= 0xff (char*) 117 | - 2 bytes if dk_size <= 0xffff (int16_t*) 118 | - 4 bytes if dk_size <= 0xffffffff (int32_t*) 119 | - 8 bytes otherwise (int64_t*) 120 | 121 | Dynamically sized, SIZEOF_VOID_P is minimum. */ 122 | char dk_indices[]; /* char is required to avoid strict aliasing. */   // 存入的entries 123 | 124 | /* "PyDictKeyEntry dk_entries[dk_usable];" array follows: 125 | see the DK_ENTRIES() macro */ 126 | }; 127 | ``` 128 | 129 | 相关数据结构的内存布局为; 130 | ![python_dict_mem](dict-mem.png) 131 | 132 | ## Python 字典示例 133 | 134 | 本次示例脚本如下: 135 | 136 | ```python 137 | d = {} 138 | d['1']='2' 139 | d['1']='e' 140 | d.pop('1') 141 | 142 | ``` 143 | 144 | 通过 Python 的反汇编工具获取字节码; 145 | 146 | ```shell 147 | python -m dis dict_test.py 148 | ``` 149 | 150 | 输出的字节码如下; 151 | 152 | ```shell 153 | 2 0 BUILD_MAP 0 154 | 2 STORE_NAME 0 (d) 155 | 156 | 3 4 LOAD_CONST 0 ('2') 157 | 6 LOAD_NAME 0 (d) 158 | 8 LOAD_CONST 1 ('1') 159 | 10 STORE_SUBSCR 160 | 161 | 4 12 LOAD_CONST 2 ('e') 162 | 14 LOAD_NAME 0 (d) 163 | 16 LOAD_CONST 1 ('1') 164 | 18 STORE_SUBSCR 165 | 166 | 5 20 LOAD_NAME 0 (d) 167 | 22 LOAD_METHOD 1 (pop) 168 | 24 LOAD_CONST 1 ('1') 169 | 26 CALL_METHOD 1 170 | 28 POP_TOP 171 | 30 LOAD_CONST 3 (None) 172 | 32 RETURN_VALUE 173 | ``` 174 | 175 | 通过字节码指令可知,首先调用了 BUILD_MAP 来创建一个新的字典,接着就对新建的字典 d 进行了赋值操作与更新操作,最后调用了 pop 方法删除一个 key。接下来就详细分析一下相关流程。 176 | 177 | ## 字典的初始化流程 178 | 179 | 通过查找 BUILD_MAP 的虚拟机执行函数; 180 | 181 | `源文件:`[Python/ceval.c](https://github.com/python/cpython/blob/v3.7.0/Python/ceval.c#L2357) 182 | 183 | ```c 184 | // Python/ceval.c 185 | switch (opcode) { 186 | ... 187 | 188 | TARGET(BUILD_MAP) { 189 | Py_ssize_t i; 190 | PyObject *map = _PyDict_NewPresized((Py_ssize_t)oparg); // 新建并初始化一个字典 191 | if (map == NULL) 192 | goto error;  // 如果新建失败则报错 193 | for (i = oparg; i > 0; i--) {   // 检查在新建的过程中是否通过参数传值 194 | int err; 195 | PyObject *key = PEEK(2*i); 196 | PyObject *value = PEEK(2*i - 1); 197 | err = PyDict_SetItem(map, key, value);      // 找到对应的值并讲该值设置到map中 198 | if (err != 0) {                        // 检查是否报错 199 | Py_DECREF(map); 200 | goto error;                        // 如果错误就报错处理 201 | } 202 | } 203 | 204 | while (oparg--) { 205 | Py_DECREF(POP());                       // 弹出栈上输入参数的引用 206 | Py_DECREF(POP()); 207 | } 208 | PUSH(map);                              // 讲生成的map压栈 209 | DISPATCH();                             // 检查是否需要执行下一条字节码指令 210 | } 211 | } 212 | ``` 213 | 214 | 从该函数的执行可知,初始化的函数是从\_PyDict_NewPresized 开始,该函数就是生成并初始化一个字典; 215 | 216 | `源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L1240) 217 | 218 | ```c 219 | // Objects/dictobject.c 220 | 221 | PyObject * 222 | _PyDict_NewPresized(Py_ssize_t minused) 223 | { 224 | const Py_ssize_t max_presize = 128 * 1024;  // 字典最大的容量 225 | Py_ssize_t newsize; 226 | PyDictKeysObject *new_keys; 227 | 228 | /* There are no strict guarantee that returned dict can contain minused 229 | * items without resize. So we create medium size dict instead of very 230 | * large dict or MemoryError. 231 | */ 232 | if (minused > USABLE_FRACTION(max_presize)) { // 检查传入的数量是否超过最大值 233 | newsize = max_presize; 234 | } 235 | else { 236 | Py_ssize_t minsize = ESTIMATE_SIZE(minused); // 获取最小的值,在新建一个空的字典的时候该值为0 237 | newsize = PyDict_MINSIZE; // 设置字典的最小值 为8 238 | while (newsize < minsize) { // 如果传入的值大于最小值则调整newsize 大小 239 | newsize <<= 1; 240 | } 241 | } 242 | assert(IS_POWER_OF_2(newsize)); 243 | 244 | new_keys = new_keys_object(newsize); // 生成并初始化一个PyDictKeysObject对象 245 | if (new_keys == NULL) 246 | return NULL; 247 | return new_dict(new_keys, NULL); // 生成一个新的对象并返回 248 | } 249 | ``` 250 | 251 | 首先,先计算出需要生成的字典的大小,然后再初始化一个 PyDictKeysObject,最后就生成一个 PyDictObject 返回。继续查看 new_keys_object 的执行流程; 252 | 253 | `源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L503) 254 | 255 | ```c 256 | // Objects/dictobject.c 257 | 258 | static PyDictKeysObject *new_keys_object(Py_ssize_t size) 259 | { 260 | PyDictKeysObject *dk; 261 | Py_ssize_t es, usable; 262 | 263 | assert(size >= PyDict_MINSIZE); // 检查size是否大于最小size 264 | assert(IS_POWER_OF_2(size)); // 检查是否是2的倍数 265 | 266 | usable = USABLE_FRACTION(size); // 检查是否可用  根据经验在1/2和2/3之间效果最好 267 | if (size <= 0xff) { 268 | es = 1; 269 | } 270 | else if (size <= 0xffff) { 271 | es = 2; 272 | } 273 | #if SIZEOF_VOID_P > 4 274 | else if (size <= 0xffffffff) { 275 | es = 4; 276 | } 277 | #endif 278 | else { 279 | es = sizeof(Py_ssize_t); 280 | } 281 | 282 | if (size == PyDict_MINSIZE && numfreekeys > 0) {      // 是否有缓存,如果有缓存就选择缓存中的dk 283 | dk = keys_free_list[--numfreekeys]; 284 | } 285 | else { 286 | dk = PyObject_MALLOC(sizeof(PyDictKeysObject) 287 | + es * size 288 | + sizeof(PyDictKeyEntry) * usable); // 没有缓存可使用的字典则申请内存生成一个 289 | if (dk == NULL) { 290 | PyErr_NoMemory(); 291 | return NULL; 292 | } 293 | } 294 | DK_DEBUG_INCREF dk->dk_refcnt = 1; // 设置引用计数 295 | dk->dk_size = size; // 设置大小 296 | dk->dk_usable = usable; // 设置是否可用 297 | dk->dk_lookup = lookdict_unicode_nodummy; // 设置查找函数 298 | dk->dk_nentries = 0; 299 | memset(&dk->dk_indices[0], 0xff, es * size); // 将申请的内存置空 300 | memset(DK_ENTRIES(dk), 0, sizeof(PyDictKeyEntry) * usable); 301 | return dk; 302 | } 303 | ``` 304 | 305 | 主要就是通过传入的 size,检查是否超过设置的大小,检查是否有缓存的字典数据可用,如果没有则申请内存重新生成一个 dk,最后进行申请到的内存讲内容清空。接着就会进行 new_dict 初始化数据; 306 | 307 | `源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L568) 308 | 309 | ```c 310 | // Objects/dictobject.c 311 | 312 | /* Consumes a reference to the keys object */ 313 | static PyObject * 314 | new_dict(PyDictKeysObject *keys, PyObject **values) 315 | { 316 | PyDictObject *mp; 317 | assert(keys != NULL); 318 | if (numfree) {                            // 判断缓冲池是否有 319 | mp = free_list[--numfree]; 320 | assert (mp != NULL); 321 | assert (Py_TYPE(mp) == &PyDict_Type);  322 | _Py_NewReference((PyObject *)mp);              // 使用缓冲池对象     323 | } 324 | else { 325 | mp = PyObject_GC_New(PyDictObject, &PyDict_Type);    // 缓冲池没有则申请新的对象并初始化 326 | if (mp == NULL) { 327 | DK_DECREF(keys); 328 | free_values(values); 329 | return NULL; 330 | } 331 | } 332 | mp->ma_keys = keys; 333 | mp->ma_values = values; 334 | mp->ma_used = 0;                           // 设置ma_used为0 335 | mp->ma_version_tag = DICT_NEXT_VERSION(); 336 | assert(_PyDict_CheckConsistency(mp)); 337 | return (PyObject *)mp; 338 | } 339 | ``` 340 | 341 | new_dict 就是根据 keys,values 设置到从缓冲池或者新生成一个 dict 对象,最后返回。至此,dict 的创建工作已经完成。 342 | 343 | ## 字典的插入与查找 344 | 345 | 通过字节码的指令 STORE_SUBSCR 可知,该命令就是讲'1'作为 key, '2'作为 value 插入到 d 中,此时查看该执行函数; 346 | 347 | `源文件:`[Python/ceval.c](https://github.com/python/cpython/blob/v3.7.0/Python/ceval.c#L1561) 348 | 349 | ```c 350 | // Python/ceval.c 351 | switch (opcode) { 352 | ... 353 | 354 | TARGET(STORE_SUBSCR) { 355 | PyObject *sub = TOP(); // 第一个值为key 356 | PyObject *container = SECOND(); // 该为字典对象 357 | PyObject *v = THIRD(); // 该为value 358 | int err; 359 | STACKADJ(-3); 360 | /* container[sub] = v */ 361 | err = PyObject_SetItem(container, sub, v); // 调用该方法设置值 362 | Py_DECREF(v); 363 | Py_DECREF(container); 364 | Py_DECREF(sub); 365 | if (err != 0) 366 | goto error; 367 | DISPATCH(); 368 | } 369 | } 370 | ``` 371 | 372 | 此时,从栈中取出相关参数,并将这些值传入 PyObject_SetItem 函数进行处理设置值; 373 | 374 | `源文件:`[Objects/abstract.c](https://github.com/python/cpython/blob/v3.7.0/Objects/abstract.c#L186) 375 | 376 | ```c 377 | // Objects/abstract.c 378 | int 379 | PyObject_SetItem(PyObject *o, PyObject *key, PyObject *value) 380 | { 381 | PyMappingMethods *m; 382 | 383 | if (o == NULL || key == NULL || value == NULL) {           // 检查是否为空如果任一为空则报错 384 | null_error(); 385 | return -1; 386 | } 387 | m = o->ob_type->tp_as_mapping;                      // 获取类型的tp_as_mapping方法集      388 | if (m && m->mp_ass_subscript)                       // 如果有设置该类型 389 | return m->mp_ass_subscript(o, key, value); // 调用该mp_ass_subscript方法 390 | 391 | if (o->ob_type->tp_as_sequence) { // 获取作为队列的操作集 392 | if (PyIndex_Check(key)) {                       // 检查key是否是索引 393 | Py_ssize_t key_value; 394 | key_value = PyNumber_AsSsize_t(key, PyExc_IndexError);  395 | if (key_value == -1 && PyErr_Occurred()) 396 | return -1; 397 | return PySequence_SetItem(o, key_value, value);       // 调用索引插入 398 | } 399 | else if (o->ob_type->tp_as_sequence->sq_ass_item) { 400 | type_error("sequence index must be " 401 | "integer, not '%.200s'", key); 402 | return -1; 403 | } 404 | } 405 | 406 | type_error("'%.200s' object does not support item assignment", o);   // 则该类型对象不支持设置 407 | return -1; 408 | } 409 | ``` 410 | 411 | 其中就调用了字典的 tp_as_mapping 的方法集,并调用了该方法集的 mp_ass_subscript 方法;此时我们分析一下,dict 的 tp_as_mapping 的方法集。此时就调用了 tp_as_mapping 的 mp_ass_subscript 方法,此时就是调用 dict 的 dict_ass_sub 方法; 412 | 413 | `源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L2040) 414 | 415 | ```c 416 | // Objects/dictobject.c 417 | static int 418 | dict_ass_sub(PyDictObject *mp, PyObject *v, PyObject *w) 419 | { 420 | if (w == NULL) 421 | return PyDict_DelItem((PyObject *)mp, v); 422 | else 423 | return PyDict_SetItem((PyObject *)mp, v, w); 424 | } 425 | ``` 426 | 427 | 可知,删除一个 key 就是 PyDict_DelItem,设置一个 key 就是 PyDict_SetItem; 428 | 429 | `源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L1433) 430 | 431 | ```c 432 | // Objects/dictobject.c 433 | int 434 | PyDict_SetItem(PyObject *op, PyObject *key, PyObject *value) 435 | { 436 | PyDictObject *mp; 437 | Py_hash_t hash; 438 | if (!PyDict_Check(op)) {            // 检查是否是字典类型 439 | PyErr_BadInternalCall(); 440 | return -1; 441 | } 442 | assert(key); 443 | assert(value); 444 | mp = (PyDictObject *)op; 445 | if (!PyUnicode_CheckExact(key) || 446 | (hash = ((PyASCIIObject *) key)->hash) == -1)  // 检查传入的key是否hash为-1 447 | { 448 | hash = PyObject_Hash(key); // 生成hash调用key对应的tp_hash方法,在本例中传入的是str类型,则调用str类型的tp_hash方法 449 | if (hash == -1) 450 | return -1; 451 | } 452 | 453 | /* insertdict() handles any resizing that might be necessary */ 454 | return insertdict(mp, key, hash, value); // 生成hash调用key对应的tp_hash方法 455 | } 456 | 457 | ``` 458 | 459 | insertdict 方法就是将生成的方法,插入到字典中去; 460 | 461 | `源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L987) 462 | 463 | ```c 464 | // Objects/dictobject.c 465 | static int 466 | insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value) 467 | { 468 | PyObject *old_value; 469 | PyDictKeyEntry *ep; 470 | 471 | Py_INCREF(key); 472 | Py_INCREF(value); 473 | if (mp->ma_values != NULL && !PyUnicode_CheckExact(key)) { 474 | if (insertion_resize(mp) < 0) // 重新设置mp的大小 如果ma_values有值 475 | goto Fail; 476 | } 477 | 478 | Py_ssize_t ix = mp->ma_keys->dk_lookup(mp, key, hash, &old_value);     // 调用查找方法 479 | if (ix == DKIX_ERROR) 480 | goto Fail; 481 | 482 | assert(PyUnicode_CheckExact(key) || mp->ma_keys->dk_lookup == lookdict); 483 | MAINTAIN_TRACKING(mp, key, value); // 检查mp key values是否需要加入垃圾回收 484 | 485 | /* When insertion order is different from shared key, we can't share 486 | * the key anymore. Convert this instance to combine table. 487 | */ 488 | if (_PyDict_HasSplitTable(mp) && 489 | ((ix >= 0 && old_value == NULL && mp->ma_used != ix) || 490 | (ix == DKIX_EMPTY && mp->ma_used != mp->ma_keys->dk_nentries))) {  // 检查是否是分离表,如果没查找到旧值并且 491 | if (insertion_resize(mp) < 0)                         // 重新设置该字典大小 492 | goto Fail; 493 | ix = DKIX_EMPTY; 494 | } 495 | 496 | if (ix == DKIX_EMPTY) { 497 | /* Insert into new slot. */ 498 | assert(old_value == NULL); 499 | if (mp->ma_keys->dk_usable <= 0) {                      // 如果可用的值小于0 500 | /* Need to resize. */ 501 | if (insertion_resize(mp) < 0)                       // 需要重新扩展字典大小 502 | goto Fail; 503 | } 504 | Py_ssize_t hashpos = find_empty_slot(mp->ma_keys, hash);         // 查找一个可用的hash位置 505 | ep = &DK_ENTRIES(mp->ma_keys)[mp->ma_keys->dk_nentries];         // 获取存取的地址 506 | dk_set_index(mp->ma_keys, hashpos, mp->ma_keys->dk_nentries);      // 设置该值 507 | ep->me_key = key;                                 // 保存key 508 | ep->me_hash = hash; // 保存计算得出的hash值 509 | if (mp->ma_values) {                               // 如果mp的ma_values有值 510 | assert (mp->ma_values[mp->ma_keys->dk_nentries] == NULL); 511 | mp->ma_values[mp->ma_keys->dk_nentries] = value;           // 设置该key对应的value 512 | } 513 | else { 514 | ep->me_value = value; // 直接讲value设置到entry上面 515 | } 516 | mp->ma_used++;                                   // 使用个数加1 517 | mp->ma_version_tag = DICT_NEXT_VERSION();   518 | mp->ma_keys->dk_usable--;                            // 可用减1 519 | mp->ma_keys->dk_nentries++; 520 | assert(mp->ma_keys->dk_usable >= 0); 521 | assert(_PyDict_CheckConsistency(mp)); 522 | return 0; 523 | } 524 | 525 | if (_PyDict_HasSplitTable(mp)) { // 如果是分离的 526 | mp->ma_values[ix] = value; // 直接设置ma_values对应的ix到values中 527 | if (old_value == NULL) { 528 | /* pending state */ 529 | assert(ix == mp->ma_used); 530 | mp->ma_used++;                               // 使用加1 531 | } 532 | } 533 | else { 534 | assert(old_value != NULL); 535 | DK_ENTRIES(mp->ma_keys)[ix].me_value = value; 536 | } 537 | 538 | mp->ma_version_tag = DICT_NEXT_VERSION(); 539 | Py_XDECREF(old_value); /* which **CAN** re-enter (see issue #22653) */ 540 | assert(_PyDict_CheckConsistency(mp)); 541 | Py_DECREF(key); 542 | return 0; 543 | 544 | Fail: 545 | Py_DECREF(value); 546 | Py_DECREF(key); 547 | return -1; 548 | } 549 | ``` 550 | 551 | 首先会调用相关的查找方法,去查找待搜索的值是否已经存在字典中,如果当前字典数据已经满了则会按照增长大小的函数生成一个新的字典,并把旧数据设置到新的字典中,当找到的字典匹配时则返回。 552 | 553 | 其中 dk_lookup 对应的方法,在初始化之后对应的是 lookdict_unicode_nodummy; 554 | 555 | `源文件:`[Objects/dictobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/dictobject.c#L813) 556 | 557 | ```c 558 | // Objects/dictobject.c 559 | 560 | /* Faster version of lookdict_unicode when it is known that no keys 561 | * will be present. */ 562 | static Py_ssize_t _Py_HOT_FUNCTION 563 | lookdict_unicode_nodummy(PyDictObject *mp, PyObject *key, 564 | Py_hash_t hash, PyObject **value_addr) 565 | { 566 | assert(mp->ma_values == NULL); 567 | /* Make sure this function doesn't have to handle non-unicode keys, 568 | including subclasses of str; e.g., one reason to subclass 569 | unicodes is to override __eq__, and for speed we don't cater to 570 | that here. */ 571 | if (!PyUnicode_CheckExact(key)) {                     // 检查如果不是unicode则直接调用lookdict方法查找 572 | mp->ma_keys->dk_lookup = lookdict; 573 | return lookdict(mp, key, hash, value_addr); 574 | } 575 | 576 | PyDictKeyEntry *ep0 = DK_ENTRIES(mp->ma_keys);             // 获取keys的首个元素地址 577 | size_t mask = DK_MASK(mp->ma_keys);                    // 获取大小 578 | size_t perturb = (size_t)hash; 579 | size_t i = (size_t)hash & mask;                       // 获取生成的最终的值                  580 | 581 | for (;;) { 582 | Py_ssize_t ix = dk_get_index(mp->ma_keys, i); // 便利ma_keys key列表 583 | assert (ix != DKIX_DUMMY);                     // 判断不能为空 584 | if (ix == DKIX_EMPTY) { // 如果为空则证明找到一个可以使用的 585 | *value_addr = NULL;                       // 讲key对应的value设置为空 586 | return DKIX_EMPTY;                        // 返回 587 | } 588 | PyDictKeyEntry *ep = &ep0[ix];             // 获取该位置元素值 589 | assert(ep->me_key != NULL); 590 | assert(PyUnicode_CheckExact(ep->me_key)); 591 | if (ep->me_key == key || 592 | (ep->me_hash == hash && unicode_eq(ep->me_key, key))) {  // 如果key相同 hash值也相同 593 | *value_addr = ep->me_value;                    // 将该值赋值 594 | return ix; 595 | } 596 | perturb >>= PERTURB_SHIFT;                      // 偏移 597 | i = mask & (i*5 + perturb + 1);                   // 获取下一个位置 598 | } 599 | Py_UNREACHABLE(); 600 | } 601 | ``` 602 | 603 | 该函数的主要工作就是查找,字典中是否有空余的值,或者如果找到了满足 hash 值与 key 相同的就将 value 设置为找到的值(这也是字典查找的核心逻辑)。至此,字典的插入的大致流程已经分析完毕。 604 | 605 | ## Python 字典的操作测试 606 | 607 | 现在我们动手观看一下具体的操作实例,首先声明,该例子仅供调试使用,目前调试的字典的 key 与 value 都是 float 类型并且不能 del 或者 pop 其中的 key。操作字典如下所示; 608 | 609 | ```python 610 | d = {20000:2} 611 | d[1] = 2 612 | d[3] = 2 613 | ``` 614 | 615 | 首先,讲如下代码插入到 dictobject.c 的 1060 行; 616 | 617 | ```c 618 | // 测试代码 619 | PyObject* key1 = PyLong_FromLong(20000); 620 | Py_hash_t hash1 = PyObject_Hash(key1); 621 | PyObject* old_value1; 622 | Py_ssize_t ix1 = mp->ma_keys->dk_lookup(mp, key1, hash1, &old_value1); 623 | if (ix1 == 0){ 624 | PyLongObject* give; 625 | give = (PyLongObject* )key1; 626 | printf("found value : %ld\n", give->ob_digit[0]); 627 | PyDictKeyEntry *ep01 = DK_ENTRIES(mp->ma_keys); 628 | int i, count; 629 | count = mp->ma_used; 630 | int size_count, j; 631 | size_count = mp->ma_keys->dk_size; 632 | printf("%s ", mp->ma_keys->dk_indices); 633 | int8_t *indices = (int8_t*)(mp->ma_keys->dk_indices); 634 | printf("indices index values :"); 635 | for (j=0; jme_key; 641 | printf("size : %d ", mp->ma_keys->dk_size); 642 | printf("found value while  key : %ld ", give->ob_digit[0]); 643 | give = (PyLongObject* )ep01->me_value; 644 | printf("value : %ld\n", give->ob_digit[0]); 645 | ep01++; 646 | } 647 | } 648 | ``` 649 | 650 | 然后编译运行; 651 | 652 | ```python 653 | Python 3.7.3 (default, May 22 2019, 16:17:57) 654 | [GCC 7.3.0] on linux 655 | Type "help", "copyright", "credits" or "license" for more information. 656 | >>> d = {20000:2} 657 | found value : 20000 658 | indices index values :0 -1 -1 -1 -1 -1 -1 -1 659 | size : 8 found value while  key : 20000 value : 2 660 | ``` 661 | 662 | 其中为什么初始化的时候输入 20000,是根据代码找到相关的 key 值,因为字典也被 python 自身实现的结构中引用了多次,所以我们就设置了一个特殊值来跟踪我们想要的字典;当 d 初始化的时候,就输出如上所示内容;我们接下来继续操作; 663 | 664 | ```python 665 | >>> d = {20000:2} 666 | found value : 20000 667 | indices index values :0 -1 -1 -1 -1 -1 -1 -1 668 | size : 8 found value while  key : 20000 value : 2 669 | >>> d[2] = 3 670 | found value : 20000 671 | indices index values :0 -1 1 -1 -1 -1 -1 -1 672 | size : 8 found value while  key : 20000 value : 2 673 | size : 8 found value while  key : 2 value : 3 674 | >>> d[3] = 4 675 | found value : 20000 676 | indices index values :0 -1 1 2 -1 -1 -1 -1 677 | size : 8 found value while  key : 20000 value : 2 678 | size : 8 found value while  key : 2 value : 3 679 | size : 8 found value while  key : 3 value : 4 680 | >>> d[5] = 6 681 | found value : 20000 682 | indices index values :0 -1 1 2 -1 3 -1 -1 683 | size : 8 found value while  key : 20000 value : 2 684 | size : 8 found value while  key : 2 value : 3 685 | size : 8 found value while  key : 3 value : 4 686 | size : 8 found value while  key : 5 value : 6 687 | >>> d[7] = 8 688 | found value : 20000 689 | indices index values :0 -1 1 2 -1 3 -1 4 690 | size : 8 found value while  key : 20000 value : 2 691 | size : 8 found value while  key : 2 value : 3 692 | size : 8 found value while  key : 3 value : 4 693 | size : 8 found value while  key : 5 value : 6 694 | size : 8 found value while  key : 7 value : 8 695 | ``` 696 | 697 | 此后我们一直添加值进 d,从输出信息可知,index 就是记录了 PyDictKeyEntry 的索引值,-1 就表示该处未使用。 698 | 当我们继续向 d 中添加内容时; 699 | 700 | ```python 701 | >>> d[9] = 10 702 | found value : 20000 703 | indices index values :0 -1 1 2 -1 3 -1 4 -1 5 -1 -1 -1 -1 -1 -1 704 | size : 16 found value while  key : 20000 value : 2 705 | size : 16 found value while  key : 2 value : 3 706 | size : 16 found value while  key : 3 value : 4 707 | size : 16 found value while  key : 5 value : 6 708 | size : 16 found value while  key : 7 value : 8 709 | size : 16 found value while  key : 9 value : 10 710 | >>> d[10] = 11 711 | found value : 20000 712 | indices index values :0 -1 1 2 -1 3 -1 4 -1 5 6 -1 -1 -1 -1 -1 713 | size : 16 found value while  key : 20000 value : 2 714 | size : 16 found value while  key : 2 value : 3 715 | size : 16 found value while  key : 3 value : 4 716 | size : 16 found value while  key : 5 value : 6 717 | size : 16 found value while  key : 7 value : 8 718 | size : 16 found value while  key : 9 value : 10 719 | size : 16 found value while  key : 10 value : 11 720 | ``` 721 | 722 | 从输出内容可知,字典的大小随之改变了,这也说明了 python 字典的最佳大小容量限定在 1/2 到 2/3 之间,如果超过这个阈值则字典就会自动扩容,扩容的策略大家可详细查看源码。 723 | -------------------------------------------------------------------------------- /objects/list-object/PyListStructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/list-object/PyListStructure.png -------------------------------------------------------------------------------- /objects/list-object/index.md: -------------------------------------------------------------------------------- 1 | # Python List 对象 2 | 3 | 在Python中的list可以存放任何类型的数据,查看`PyListObject`可以发现,list实际存放的是PyObject* 指针 4 | 5 | ## PyListObject 6 | 7 | `源文件:`[Include/listobject.h](https://github.com/python/cpython/blob/v3.7.0/Include/listobject.h#L23) 8 | 9 | ```c 10 | // listobject.h 11 | 12 | typedef struct { 13 | PyObject_VAR_HEAD 14 | /* Vector of pointers to list elements. list[0] is ob_item[0], etc. */ 15 | PyObject **ob_item; 16 | 17 | /* ob_item contains space for 'allocated' elements. The number 18 | * currently in use is ob_size. 19 | * Invariants: 20 | * 0 <= ob_size <= allocated 21 | * len(list) == ob_size 22 | * ob_item == NULL implies ob_size == allocated == 0 23 | * list.sort() temporarily sets allocated to -1 to detect mutations. 24 | * 25 | * Items must normally not be NULL, except during construction when 26 | * the list is not yet visible outside the function that builds it. 27 | */ 28 | 29 | // 可容纳元素的总数 30 | Py_ssize_t allocated; 31 | } PyListObject; 32 | ``` 33 | 34 | 示例 35 | ```python 36 | lst = [] 37 | lst.append(1) 38 | ``` 39 | 40 | 其存储结构如下图 41 | 42 | ![PyList structure](PyListStructure.png) 43 | 44 | 45 | 46 | ## PyListObject对象的一些操作 47 | 48 | - 创建PyListObject PyList_New 49 | - 对象赋值 PyList_SetItem 50 | - 获取元素 PyList_GetItem 51 | - 插入元素 PyList_Insert 52 | - 追加元素 PyList_Append 53 | - 移除元素 list_remove 54 | - 调整list大小 list_resize 55 | 56 | ### PyList_New 创建对象 57 | 58 | 为了避免频繁的申请内存空间,创建PyListObject的时候会先检查缓冲池是否有可用空间 59 | 60 | `源文件:`[Objects/listobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/listobject.c#L136) 61 | 62 | ```c 63 | // listobject.c 64 | 65 | PyObject * 66 | PyList_New(Py_ssize_t size) 67 | { 68 | PyListObject *op; 69 | #ifdef SHOW_ALLOC_COUNT 70 | static int initialized = 0; 71 | if (!initialized) { 72 | Py_AtExit(show_alloc); 73 | initialized = 1; 74 | } 75 | #endif 76 | 77 | // size 合法性检查 78 | if (size < 0) { 79 | PyErr_BadInternalCall(); 80 | return NULL; 81 | } 82 | 83 | // PyListObject对象缓冲池是否有可用空间 84 | if (numfree) { 85 | numfree--; 86 | op = free_list[numfree]; 87 | _Py_NewReference((PyObject *)op); 88 | #ifdef SHOW_ALLOC_COUNT 89 | count_reuse++; 90 | #endif 91 | } else { 92 | // 缓冲池满只能向系统申请内存 93 | op = PyObject_GC_New(PyListObject, &PyList_Type); 94 | if (op == NULL) 95 | return NULL; 96 | #ifdef SHOW_ALLOC_COUNT 97 | count_alloc++; 98 | #endif 99 | } 100 | if (size <= 0) 101 | op->ob_item = NULL; 102 | else { 103 | op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *)); 104 | if (op->ob_item == NULL) { 105 | Py_DECREF(op); 106 | return PyErr_NoMemory(); 107 | } 108 | } 109 | Py_SIZE(op) = size; 110 | op->allocated = size; 111 | _PyObject_GC_TRACK(op); 112 | return (PyObject *) op; 113 | } 114 | ``` 115 | 116 | PyListObject缓冲池默认大小为80 `源文件:`[Include/listobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/listobject.c#L101) 117 | 118 | ```c 119 | // listobject.c 120 | 121 | /* Empty list reuse scheme to save calls to malloc and free */ 122 | #ifndef PyList_MAXFREELIST 123 | #define PyList_MAXFREELIST 80 124 | #endif 125 | static PyListObject *free_list[PyList_MAXFREELIST]; 126 | static int numfree = 0; 127 | ``` 128 | 129 | ### PyList_SetItem 元素赋值 130 | 131 | `源文件:`[Objects/listobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/listobject.c#L215) 132 | 133 | ```c 134 | // listobject.c 135 | 136 | int 137 | PyList_SetItem(PyObject *op, Py_ssize_t i, 138 | PyObject *newitem) 139 | { 140 | PyObject **p; 141 | if (!PyList_Check(op)) { 142 | Py_XDECREF(newitem); 143 | PyErr_BadInternalCall(); 144 | return -1; 145 | } 146 | if (i < 0 || i >= Py_SIZE(op)) { 147 | Py_XDECREF(newitem); 148 | PyErr_SetString(PyExc_IndexError, 149 | "list assignment index out of range"); 150 | return -1; 151 | } 152 | p = ((PyListObject *)op) -> ob_item + i; 153 | Py_XSETREF(*p, newitem); 154 | return 0; 155 | } 156 | ``` 157 | 158 | 元素赋值的示例 159 | 160 | ```python 161 | lst = [0, 1, 2] 162 | lst[0] = 3 163 | # 这里 lst[0] = 3 会调用 PyList_SetItem 函数 164 | ``` 165 | 166 | 167 | ### PyList_GetItem 获取元素 168 | 169 | `源文件:`[Objects/listobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/listobject.c#L195) 170 | 171 | ```c 172 | // Objects/listobject.c 173 | 174 | PyObject * 175 | PyList_GetItem(PyObject *op, Py_ssize_t i) 176 | { 177 | if (!PyList_Check(op)) { 178 | PyErr_BadInternalCall(); 179 | return NULL; 180 | } 181 | if (i < 0 || i >= Py_SIZE(op)) { 182 | if (indexerr == NULL) { 183 | indexerr = PyUnicode_FromString( 184 | "list index out of range"); 185 | if (indexerr == NULL) 186 | return NULL; 187 | } 188 | PyErr_SetObject(PyExc_IndexError, indexerr); 189 | return NULL; 190 | } 191 | return ((PyListObject *)op) -> ob_item[i]; 192 | } 193 | ``` 194 | 195 | 获取元素的示例 196 | 197 | ```python 198 | lst = [1, 2, 3, 4] 199 | print(lst[3]) 200 | # lst[3] 实际调用的就是 PyList_GetItem 201 | # 根据索引返回对应的元素 202 | ``` 203 | 204 | 205 | ### PyList_Append 追加元素 206 | 207 | PyList_Append 调用 app1 208 | 209 | ```c 210 | int 211 | PyList_Append(PyObject *op, PyObject *newitem) 212 | { 213 | if (PyList_Check(op) && (newitem != NULL)) 214 | return app1((PyListObject *)op, newitem); 215 | PyErr_BadInternalCall(); 216 | return -1; 217 | } 218 | ``` 219 | 220 | `源文件:`[Objects/listobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/listobject.c#L279) 221 | 222 | ```c 223 | // Objects/listobject.c 224 | 225 | static int 226 | app1(PyListObject *self, PyObject *v) 227 | { 228 | Py_ssize_t n = PyList_GET_SIZE(self); 229 | 230 | assert (v != NULL); 231 | if (n == PY_SSIZE_T_MAX) { 232 | PyErr_SetString(PyExc_OverflowError, 233 | "cannot add more objects to list"); 234 | return -1; 235 | } 236 | 237 | if (list_resize(self, n+1) < 0) 238 | return -1; 239 | 240 | Py_INCREF(v); 241 | PyList_SET_ITEM(self, n, v); 242 | return 0; 243 | } 244 | ``` 245 | 246 | 从`app1`代码可以看出追加元素操作大致流程如下 247 | - 调用list_resize,将list大小加一 248 | - 将元素插入list尾部 249 | 250 | ### PyList_Insert 插入元素 251 | 252 | PyList_Insert 调用 ins1 253 | 254 | ```c 255 | int 256 | PyList_Insert(PyObject *op, Py_ssize_t where, PyObject *newitem) 257 | { 258 | if (!PyList_Check(op)) { 259 | PyErr_BadInternalCall(); 260 | return -1; 261 | } 262 | return ins1((PyListObject *)op, where, newitem); 263 | } 264 | ``` 265 | 266 | `源文件:`[Objects/listobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/listobject.c#L236) 267 | 268 | ```c 269 | // Objects/listobject.c 270 | 271 | static int 272 | ins1(PyListObject *self, Py_ssize_t where, PyObject *v) 273 | { 274 | Py_ssize_t i, n = Py_SIZE(self); 275 | PyObject **items; 276 | if (v == NULL) { 277 | PyErr_BadInternalCall(); 278 | return -1; 279 | } 280 | if (n == PY_SSIZE_T_MAX) { 281 | PyErr_SetString(PyExc_OverflowError, 282 | "cannot add more objects to list"); 283 | return -1; 284 | } 285 | 286 | if (list_resize(self, n+1) < 0) 287 | return -1; 288 | 289 | if (where < 0) { 290 | where += n; 291 | if (where < 0) 292 | where = 0; 293 | } 294 | if (where > n) 295 | where = n; 296 | items = self->ob_item; 297 | for (i = n; --i >= where; ) 298 | items[i+1] = items[i]; 299 | Py_INCREF(v); 300 | items[where] = v; 301 | return 0; 302 | } 303 | ``` 304 | 305 | 从`ins1`代码可以看出插入元素操作大致流程如下 306 | - 调用list_resize,将list大小加一 307 | - 将要插入的位置的元素都往后移一个位置 308 | - 将元素插入指定位置 309 | 310 | ### list_remove 移除元素 311 | 312 | `源文件:`[Objects/listobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/listobject.c#L2546) 313 | 314 | ```c 315 | // listobject.c 316 | 317 | static PyObject * 318 | list_remove(PyListObject *self, PyObject *value) 319 | /*[clinic end generated code: output=f087e1951a5e30d1 input=2dc2ba5bb2fb1f82]*/ 320 | { 321 | Py_ssize_t i; 322 | 323 | for (i = 0; i < Py_SIZE(self); i++) { 324 | int cmp = PyObject_RichCompareBool(self->ob_item[i], value, Py_EQ); 325 | if (cmp > 0) { 326 | if (list_ass_slice(self, i, i+1, 327 | (PyObject *)NULL) == 0) 328 | Py_RETURN_NONE; 329 | return NULL; 330 | } 331 | else if (cmp < 0) 332 | return NULL; 333 | } 334 | PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list"); 335 | return NULL; 336 | } 337 | ``` 338 | 339 | 移除元素示例 340 | 341 | ```python 342 | lst = [0, 2, 4, 3] 343 | lst.remove(3) 344 | """ 345 | lst.remove(3) 会调用 list_remove函数, 346 | list_remove函数会遍历列表,使用PyObject_RichCompareBool与目标值进行比较, 347 | 相同则调用list_ass_slice进行移除,当遍历完列表还未找到则报错 348 | """ 349 | ``` 350 | 351 | ### list_resize 调整list存储空间 352 | 353 | 随着list元素的增加,list的存储空间可能会不够用,这个时候就需要扩大list的存储空间。 354 | 随着list元素的减少,list的存储空间可能存在冗余,这个时候就需要缩小list的存储空间。 355 | 函数`list_resize`就是用于调节list存储空间大小的 356 | 357 | `源文件:`[Objects/listobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/listobject.c#L19) 358 | 359 | ```c 360 | // listobject.c 361 | 362 | static int 363 | list_resize(PyListObject *self, Py_ssize_t newsize) 364 | { 365 | PyObject **items; 366 | size_t new_allocated, num_allocated_bytes; 367 | Py_ssize_t allocated = self->allocated; 368 | 369 | /* Bypass realloc() when a previous overallocation is large enough 370 | to accommodate the newsize. If the newsize falls lower than half 371 | the allocated size, then proceed with the realloc() to shrink the list. 372 | */ 373 | if (allocated >= newsize && newsize >= (allocated >> 1)) { 374 | assert(self->ob_item != NULL || newsize == 0); 375 | Py_SIZE(self) = newsize; 376 | return 0; 377 | } 378 | 379 | /* This over-allocates proportional to the list size, making room 380 | * for additional growth. The over-allocation is mild, but is 381 | * enough to give linear-time amortized behavior over a long 382 | * sequence of appends() in the presence of a poorly-performing 383 | * system realloc(). 384 | * The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ... 385 | * Note: new_allocated won't overflow because the largest possible value 386 | * is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t. 387 | */ 388 | new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6); 389 | if (new_allocated > (size_t)PY_SSIZE_T_MAX / sizeof(PyObject *)) { 390 | PyErr_NoMemory(); 391 | return -1; 392 | } 393 | 394 | if (newsize == 0) 395 | new_allocated = 0; 396 | num_allocated_bytes = new_allocated * sizeof(PyObject *); 397 | items = (PyObject **)PyMem_Realloc(self->ob_item, num_allocated_bytes); 398 | if (items == NULL) { 399 | PyErr_NoMemory(); 400 | return -1; 401 | } 402 | self->ob_item = items; 403 | Py_SIZE(self) = newsize; 404 | self->allocated = new_allocated; 405 | return 0; 406 | } 407 | ``` 408 | 409 | 当 `allocated/2 <= newsize <= allocated` 时,list_resize只会改变 ob_size不会改变allocated。 410 | 其他情况则需要调用`PyMem_Realloc`函数分配新的空间存储列表元素。 411 | 412 | 列表allocated的增长模式是 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ... 413 | 414 | 其公式为 `new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6)` 415 | -------------------------------------------------------------------------------- /objects/long-object/index.md: -------------------------------------------------------------------------------- 1 | # Python 整数对象 2 | 3 | CPython2 的整数对象 有 `PyIntObject` 和 `PyLongObject` 这两种类型, 4 | CPython3 只保留了 `PyLongObject` 5 | 6 | 在 `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L3) 7 | 的第三行有这么一句话 `XXX The functional organization of this file is terrible` 8 | 9 | 可见这个变化不是一蹴而就的,有比较艰辛的过程,大家有兴趣可以去挖掘一下 10 | 11 | ## PyLongObject 12 | 13 | `源文件:`[Include/longobject.h](https://github.com/python/cpython/blob/v3.7.0/Include/longobject.h#L10) 14 | 15 | ```c 16 | // longobject.h 17 | 18 | typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */ 19 | ``` 20 | 21 | `源文件:`[Include/longintrepr.h](https://github.com/python/cpython/blob/v3.7.0/Include/longintrepr.h#L85) 22 | 23 | ```c 24 | // longintrepr.h 25 | /* Long integer representation. 26 | The absolute value of a number is equal to 27 | 一个数的绝对值等价于下面的表达式 28 | SUM(for i=0 through abs(ob_size)-1) ob_digit[i] * 2**(SHIFT*i) 29 | 30 | Negative numbers are represented with ob_size < 0; 31 | 负数表示为 ob_size < 0 32 | 33 | zero is represented by ob_size == 0. 34 | 整数0 用 ob_size == 0表示 35 | 36 | In a normalized number, ob_digit[abs(ob_size)-1] (the most significant 37 | digit) is never zero. Also, in all cases, for all valid i, 38 | 39 | 在一个规范的数字ob_digit[abs(ob_size)-1]()永不为0。而且,所有有效的 i 都满足以下要求 40 | 0 <= ob_digit[i] <= MASK. 41 | 42 | The allocation function takes care of allocating extra memory 43 | so that ob_digit[0] ... ob_digit[abs(ob_size)-1] are actually available. 44 | 45 | CAUTION: Generic code manipulating subtypes of PyVarObject has to 46 | aware that ints abuse ob_size's sign bit. 47 | 48 | 警告: 通用代码操作 PyVarObject 的子类型必须注意 ob_size的符号滥用问题。 49 | */ 50 | 51 | struct _longobject { 52 | PyObject_VAR_HEAD 53 | digit ob_digit[1]; 54 | }; 55 | ``` 56 | 57 | 从源码可以看出 PyLongObject 是变长对象 58 | 59 | ## 类型对象 PyLong_Type 60 | 61 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L5379) 62 | 63 | ```c 64 | // Objects/longobject.c 65 | 66 | PyTypeObject PyLong_Type = { 67 | PyVarObject_HEAD_INIT(&PyType_Type, 0) 68 | "int", /* tp_name */ 69 | offsetof(PyLongObject, ob_digit), /* tp_basicsize */ 70 | sizeof(digit), /* tp_itemsize */ 71 | long_dealloc, /* tp_dealloc */ 72 | 0, /* tp_print */ 73 | 0, /* tp_getattr */ 74 | 0, /* tp_setattr */ 75 | 0, /* tp_reserved */ 76 | long_to_decimal_string, /* tp_repr */ 77 | &long_as_number, /* tp_as_number */ 78 | 0, /* tp_as_sequence */ 79 | 0, /* tp_as_mapping */ 80 | (hashfunc)long_hash, /* tp_hash */ 81 | 0, /* tp_call */ 82 | long_to_decimal_string, /* tp_str */ 83 | PyObject_GenericGetAttr, /* tp_getattro */ 84 | 0, /* tp_setattro */ 85 | 0, /* tp_as_buffer */ 86 | Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | 87 | Py_TPFLAGS_LONG_SUBCLASS, /* tp_flags */ 88 | long_doc, /* tp_doc */ 89 | 0, /* tp_traverse */ 90 | 0, /* tp_clear */ 91 | long_richcompare, /* tp_richcompare */ 92 | 0, /* tp_weaklistoffset */ 93 | 0, /* tp_iter */ 94 | 0, /* tp_iternext */ 95 | long_methods, /* tp_methods */ 96 | 0, /* tp_members */ 97 | long_getset, /* tp_getset */ 98 | 0, /* tp_base */ 99 | 0, /* tp_dict */ 100 | 0, /* tp_descr_get */ 101 | 0, /* tp_descr_set */ 102 | 0, /* tp_dictoffset */ 103 | 0, /* tp_init */ 104 | 0, /* tp_alloc */ 105 | long_new, /* tp_new */ 106 | PyObject_Del, /* tp_free */ 107 | }; 108 | ``` 109 | 110 | ## 创建整数对象 111 | 112 | 从 PyLong_Type 可以看出,创建一个整数对象的入口函数为 long_new 113 | 114 | `源文件:`[Objects/clinic/longobject.c.h](https://github.com/python/cpython/blob/v3.7.0/Objects/clinic/longobject.c.h#L0) 115 | 116 | ```c 117 | // Objects/clinic/longobject.c.h 118 | /*[clinic input] 119 | preserve 120 | [clinic start generated code]*/ 121 | 122 | static PyObject * 123 | long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase); 124 | 125 | static PyObject * 126 | long_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) 127 | { 128 | PyObject *return_value = NULL; 129 | static const char * const _keywords[] = {"", "base", NULL}; 130 | static _PyArg_Parser _parser = {"|OO:int", _keywords, 0}; 131 | PyObject *x = NULL; 132 | PyObject *obase = NULL; 133 | 134 | if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, 135 | &x, &obase)) { 136 | goto exit; 137 | } 138 | return_value = long_new_impl(type, x, obase); 139 | 140 | exit: 141 | return return_value; 142 | } 143 | ``` 144 | 145 | 具体实现在 long_new_impl `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L4785) 146 | 147 | ```c 148 | // Objects/longobject.c 149 | 150 | /*[clinic input] 151 | @classmethod 152 | int.__new__ as long_new 153 | x: object(c_default="NULL") = 0 154 | / 155 | base as obase: object(c_default="NULL") = 10 156 | [clinic start generated code]*/ 157 | 158 | static PyObject * 159 | long_new_impl(PyTypeObject *type, PyObject *x, PyObject *obase) 160 | /*[clinic end generated code: output=e47cfe777ab0f24c input=81c98f418af9eb6f]*/ 161 | { 162 | Py_ssize_t base; 163 | 164 | if (type != &PyLong_Type) 165 | return long_subtype_new(type, x, obase); /* Wimp out */ 166 | if (x == NULL) { 167 | if (obase != NULL) { 168 | PyErr_SetString(PyExc_TypeError, 169 | "int() missing string argument"); 170 | return NULL; 171 | } 172 | return PyLong_FromLong(0L); 173 | } 174 | if (obase == NULL) 175 | return PyNumber_Long(x); 176 | 177 | base = PyNumber_AsSsize_t(obase, NULL); 178 | if (base == -1 && PyErr_Occurred()) 179 | return NULL; 180 | if ((base != 0 && base < 2) || base > 36) { 181 | PyErr_SetString(PyExc_ValueError, 182 | "int() base must be >= 2 and <= 36, or 0"); 183 | return NULL; 184 | } 185 | 186 | if (PyUnicode_Check(x)) 187 | return PyLong_FromUnicodeObject(x, (int)base); 188 | else if (PyByteArray_Check(x) || PyBytes_Check(x)) { 189 | char *string; 190 | if (PyByteArray_Check(x)) 191 | string = PyByteArray_AS_STRING(x); 192 | else 193 | string = PyBytes_AS_STRING(x); 194 | return _PyLong_FromBytes(string, Py_SIZE(x), (int)base); 195 | } 196 | else { 197 | PyErr_SetString(PyExc_TypeError, 198 | "int() can't convert non-string with explicit base"); 199 | return NULL; 200 | } 201 | } 202 | ``` 203 | 204 | 从 long_new_impl 函数可以看出有如下几种情况 205 | 206 | - x == NULL 且 obase != NULL 调用 PyLong_FromLong 207 | - obase 为 NULL 调用 PyNumber_Long 208 | - x 和 obase 都不为 NULL 209 | - PyUnicode 调用 PyLong_FromUnicodeObject,最终调用 PyLong_FromString 210 | - PyByteArray/PyBytes 调用\_PyLong_FromBytes,最终调用 PyLong_FromString 211 | 212 | ## 小整数对象 213 | 214 | 一些整数在一开始就会被初始化一直留存,当再次使用直接从小整数对象池中获取,不用频繁的申请内存。 215 | 216 | 默认的小整数范围是 [-5, 257) `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L17) 217 | 218 | ```c 219 | // Objects/longobject.c 220 | 221 | #ifndef NSMALLPOSINTS 222 | #define NSMALLPOSINTS 257 223 | #endif 224 | #ifndef NSMALLNEGINTS 225 | #define NSMALLNEGINTS 5 226 | #endif 227 | 228 | #if NSMALLNEGINTS + NSMALLPOSINTS > 0 229 | /* Small integers are preallocated in this array so that they 230 | can be shared. 231 | The integers that are preallocated are those in the range 232 | -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive). */ 233 | static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS]; 234 | #ifdef COUNT_ALLOCS 235 | Py_ssize_t quick_int_allocs, quick_neg_int_allocs; 236 | #endif 237 | 238 | static PyObject * 239 | get_small_int(sdigit ival) 240 | { 241 | PyObject *v; 242 | assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS); 243 | v = (PyObject *)&small_ints[ival + NSMALLNEGINTS]; 244 | Py_INCREF(v); 245 | #ifdef COUNT_ALLOCS 246 | if (ival >= 0) 247 | quick_int_allocs++; 248 | else 249 | quick_neg_int_allocs++; 250 | #endif 251 | return v; 252 | } 253 | #define CHECK_SMALL_INT(ival) \ 254 | do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \ 255 | return get_small_int((sdigit)ival); \ 256 | } while(0) 257 | ``` 258 | 259 | 宏 **CHECK_SMALL_INT** 会检查传入的数是否在小整数范围内,如果是直接返回。 260 | 可以在创建或复制整数对象等函数中找到 **CHECK_SMALL_INT** 的身影,以下只列出了 261 | **PyLong_FromLong**,就不一一列举了 262 | 263 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L239) 264 | 265 | ```c 266 | // Object/longobject.c 267 | 268 | PyObject * 269 | PyLong_FromLong(long ival) 270 | { 271 | PyLongObject *v; 272 | unsigned long abs_ival; 273 | unsigned long t; /* unsigned so >> doesn't propagate sign bit */ 274 | int ndigits = 0; 275 | int sign; 276 | 277 | CHECK_SMALL_INT(ival); 278 | 279 | ... 280 | } 281 | ``` 282 | 283 | ### 小整数初始化 284 | 285 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L5462) 286 | 287 | ```c 288 | // Objects/longobject.c 289 | 290 | int 291 | _PyLong_Init(void) 292 | { 293 | #if NSMALLNEGINTS + NSMALLPOSINTS > 0 294 | int ival, size; 295 | PyLongObject *v = small_ints; 296 | 297 | for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) { 298 | size = (ival < 0) ? -1 : ((ival == 0) ? 0 : 1); 299 | if (Py_TYPE(v) == &PyLong_Type) { 300 | /* The element is already initialized, most likely 301 | * the Python interpreter was initialized before. 302 | */ 303 | Py_ssize_t refcnt; 304 | PyObject* op = (PyObject*)v; 305 | 306 | refcnt = Py_REFCNT(op) < 0 ? 0 : Py_REFCNT(op); 307 | _Py_NewReference(op); 308 | /* _Py_NewReference sets the ref count to 1 but 309 | * the ref count might be larger. Set the refcnt 310 | * to the original refcnt + 1 */ 311 | Py_REFCNT(op) = refcnt + 1; 312 | assert(Py_SIZE(op) == size); 313 | assert(v->ob_digit[0] == (digit)abs(ival)); 314 | } 315 | else { 316 | (void)PyObject_INIT(v, &PyLong_Type); 317 | } 318 | Py_SIZE(v) = size; 319 | v->ob_digit[0] = (digit)abs(ival); 320 | } 321 | #endif 322 | _PyLong_Zero = PyLong_FromLong(0); 323 | if (_PyLong_Zero == NULL) 324 | return 0; 325 | _PyLong_One = PyLong_FromLong(1); 326 | if (_PyLong_One == NULL) 327 | return 0; 328 | 329 | /* initialize int_info */ 330 | if (Int_InfoType.tp_name == NULL) { 331 | if (PyStructSequence_InitType2(&Int_InfoType, &int_info_desc) < 0) 332 | return 0; 333 | } 334 | 335 | return 1; 336 | } 337 | ``` 338 | 339 | ## 整数的存储结构 340 | 341 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L1581) 342 | 343 | 在 **long_to_decimal_string_internal**中添加如下代码并重新编译安装 344 | 345 | ```c 346 | // Objects/longobject.c 347 | static int 348 | long_to_decimal_string_internal(PyObject *aa, 349 | PyObject **p_output, 350 | _PyUnicodeWriter *writer, 351 | _PyBytesWriter *bytes_writer, 352 | char **bytes_str) 353 | { 354 | PyLongObject *scratch, *a; 355 | PyObject *str = NULL; 356 | Py_ssize_t size, strlen, size_a, i, j; 357 | digit *pout, *pin, rem, tenpow; 358 | int negative; 359 | int d; 360 | enum PyUnicode_Kind kind; 361 | 362 | a = (PyLongObject *)aa; 363 | 364 | // 添加打印代码 365 | printf("ob_size = %d\n", Py_SIZE(a)); 366 | for (int index = 0; index < Py_SIZE(a); ++index) { 367 | printf("ob_digit[%d] = %d\n", index, a->ob_digit[index]); 368 | } 369 | 370 | ... 371 | } 372 | ``` 373 | 374 | 编译安装后进入 python 解释器输入如下代码 375 | 376 | ```python 377 | num = 9223372043297226753 378 | print(num) 379 | 380 | # output 381 | >>> ob_size = 3 382 | >>> ob_digit[0] = 1 383 | >>> ob_digit[1] = 6 384 | >>> ob_digit[2] = 8 385 | >>> 9223372043297226753 386 | ``` 387 | 388 | 如下图所示 389 | 390 | ![longobject storage](long-storage.png) 391 | 392 | 注:这里的 30 是由 **PyLong_SHIFT** 决定的,64 位系统中,**PyLong_SHIFT** 为 30,否则 **PyLong_SHIFT** 为 15 393 | 394 | ## 整数对象的数值操作 395 | 396 | 可以看到整数对象的数值操作较多,由于篇幅限制无法一一分析,这里只分析整数的部分操作 397 | 398 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L5341) 399 | 400 | ```c 401 | // Objects/longobject.c 402 | 403 | static PyNumberMethods long_as_number = { 404 | (binaryfunc)long_add, /*nb_add 加法 */ 405 | (binaryfunc)long_sub, /*nb_subtract 减法 */ 406 | (binaryfunc)long_mul, /*nb_multiply 乘法 */ 407 | long_mod, /*nb_remainder 取余 */ 408 | long_divmod, /*nb_divmod */ 409 | long_pow, /*nb_power 求幂 */ 410 | (unaryfunc)long_neg, /*nb_negative */ 411 | (unaryfunc)long_long, /*tp_positive */ 412 | (unaryfunc)long_abs, /*tp_absolute 绝对值 */ 413 | (inquiry)long_bool, /*tp_bool 求bool值 */ 414 | (unaryfunc)long_invert, /*nb_invert 反转 */ 415 | long_lshift, /*nb_lshift 逻辑左移 */ 416 | (binaryfunc)long_rshift, /*nb_rshift 逻辑右移 */ 417 | long_and, /*nb_and 与操作 */ 418 | long_xor, /*nb_xor 异或 */ 419 | long_or, /*nb_or 或操作 */ 420 | long_long, /*nb_int*/ 421 | 0, /*nb_reserved*/ 422 | long_float, /*nb_float*/ 423 | 0, /* nb_inplace_add */ 424 | 0, /* nb_inplace_subtract */ 425 | 0, /* nb_inplace_multiply */ 426 | 0, /* nb_inplace_remainder */ 427 | 0, /* nb_inplace_power */ 428 | 0, /* nb_inplace_lshift */ 429 | 0, /* nb_inplace_rshift */ 430 | 0, /* nb_inplace_and */ 431 | 0, /* nb_inplace_xor */ 432 | 0, /* nb_inplace_or */ 433 | long_div, /* nb_floor_divide */ 434 | long_true_divide, /* nb_true_divide */ 435 | 0, /* nb_inplace_floor_divide */ 436 | 0, /* nb_inplace_true_divide */ 437 | long_long, /* nb_index */ 438 | }; 439 | ``` 440 | 441 | ### 整数相加 442 | 443 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L3081) 444 | 445 | ```c 446 | // Objects/longobject.c 447 | 448 | static PyObject * 449 | long_add(PyLongObject *a, PyLongObject *b) 450 | { 451 | PyLongObject *z; 452 | 453 | CHECK_BINOP(a, b); 454 | 455 | if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) { 456 | return PyLong_FromLong(MEDIUM_VALUE(a) + MEDIUM_VALUE(b)); 457 | } 458 | if (Py_SIZE(a) < 0) { 459 | if (Py_SIZE(b) < 0) { 460 | z = x_add(a, b); 461 | if (z != NULL) { 462 | /* x_add received at least one multiple-digit int, 463 | and thus z must be a multiple-digit int. 464 | That also means z is not an element of 465 | small_ints, so negating it in-place is safe. */ 466 | assert(Py_REFCNT(z) == 1); 467 | Py_SIZE(z) = -(Py_SIZE(z)); 468 | } 469 | } 470 | else 471 | z = x_sub(b, a); 472 | } 473 | else { 474 | if (Py_SIZE(b) < 0) 475 | z = x_sub(a, b); 476 | else 477 | z = x_add(a, b); 478 | } 479 | return (PyObject *)z; 480 | } 481 | ``` 482 | 483 | 可以看到整数的加法运算函数 long_add 根据 a、b 的 ob_size 又细分为两个函数 (x_add 和 x_sub) 做处理 484 | 485 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L2991) 486 | 487 | ```c 488 | // Objects/longobject.c 489 | 490 | /* Add the absolute values of two integers. */ 491 | static PyLongObject * 492 | x_add(PyLongObject *a, PyLongObject *b) 493 | { 494 | Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b)); 495 | PyLongObject *z; 496 | Py_ssize_t i; 497 | digit carry = 0; 498 | 499 | /* Ensure a is the larger of the two: */ 500 | // 确保 a 大于 b 501 | if (size_a < size_b) { 502 | { PyLongObject *temp = a; a = b; b = temp; } 503 | { Py_ssize_t size_temp = size_a; 504 | size_a = size_b; 505 | size_b = size_temp; } 506 | } 507 | z = _PyLong_New(size_a+1); 508 | if (z == NULL) 509 | return NULL; 510 | for (i = 0; i < size_b; ++i) { 511 | carry += a->ob_digit[i] + b->ob_digit[i]; 512 | z->ob_digit[i] = carry & PyLong_MASK; 513 | carry >>= PyLong_SHIFT; 514 | } 515 | for (; i < size_a; ++i) { 516 | carry += a->ob_digit[i]; 517 | z->ob_digit[i] = carry & PyLong_MASK; 518 | carry >>= PyLong_SHIFT; 519 | } 520 | z->ob_digit[i] = carry; 521 | return long_normalize(z); 522 | } 523 | ``` 524 | 525 | 加法运算函数 x_add 从 ob_digit 数组的低位开始依次按位相加,carry 做进位处理,然后处理 a 对象的高位数字,最后使用 long_normalize 函数调整 ob_size,确保 ob_digit[abs(ob_size)-1]不为零,这与普通四则运算的加法运算相同,只不过进位单元不同而已 526 | 527 | ![longobject x_add](long-x-add.png) 528 | 529 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L3025) 530 | 531 | ```c 532 | // Objects/longobject.c 533 | 534 | /* Subtract the absolute values of two integers. */ 535 | 536 | static PyLongObject * 537 | x_sub(PyLongObject *a, PyLongObject *b) 538 | { 539 | Py_ssize_t size_a = Py_ABS(Py_SIZE(a)), size_b = Py_ABS(Py_SIZE(b)); 540 | PyLongObject *z; 541 | Py_ssize_t i; 542 | int sign = 1; 543 | digit borrow = 0; 544 | 545 | /* Ensure a is the larger of the two: */ 546 | // 确保 a 大于 b 547 | if (size_a < size_b) { 548 | sign = -1; 549 | { PyLongObject *temp = a; a = b; b = temp; } 550 | { Py_ssize_t size_temp = size_a; 551 | size_a = size_b; 552 | size_b = size_temp; } 553 | } 554 | else if (size_a == size_b) { 555 | /* Find highest digit where a and b differ: */ 556 | // 找到最高位 a 与 b的差异 557 | i = size_a; 558 | while (--i >= 0 && a->ob_digit[i] == b->ob_digit[i]) 559 | ; 560 | if (i < 0) 561 | return (PyLongObject *)PyLong_FromLong(0); 562 | if (a->ob_digit[i] < b->ob_digit[i]) { 563 | sign = -1; 564 | { PyLongObject *temp = a; a = b; b = temp; } 565 | } 566 | size_a = size_b = i+1; 567 | } 568 | z = _PyLong_New(size_a); 569 | if (z == NULL) 570 | return NULL; 571 | for (i = 0; i < size_b; ++i) { 572 | /* The following assumes unsigned arithmetic 573 | works module 2**N for some N>PyLong_SHIFT. */ 574 | borrow = a->ob_digit[i] - b->ob_digit[i] - borrow; 575 | z->ob_digit[i] = borrow & PyLong_MASK; 576 | borrow >>= PyLong_SHIFT; 577 | borrow &= 1; /* Keep only one sign bit */ 578 | } 579 | for (; i < size_a; ++i) { 580 | borrow = a->ob_digit[i] - borrow; 581 | z->ob_digit[i] = borrow & PyLong_MASK; 582 | borrow >>= PyLong_SHIFT; 583 | borrow &= 1; /* Keep only one sign bit */ 584 | } 585 | assert(borrow == 0); 586 | if (sign < 0) { 587 | Py_SIZE(z) = -Py_SIZE(z); 588 | } 589 | return long_normalize(z); 590 | } 591 | ``` 592 | 593 | 与普通四则运算减法相同,数不够大则向高一位借位, 594 | 减法运算函数 x_sub 的示例图如下,注:PyLong_SHIFT 为 30 595 | 596 | ![longobject x_sub](long-x-sub.png) 597 | 598 | ### 整数相乘 599 | 600 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L3547) 601 | 602 | ```c 603 | // Objects/longobject.c 604 | static PyObject * 605 | long_mul(PyLongObject *a, PyLongObject *b) 606 | { 607 | PyLongObject *z; 608 | 609 | CHECK_BINOP(a, b); 610 | 611 | /* fast path for single-digit multiplication */ 612 | if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) { 613 | stwodigits v = (stwodigits)(MEDIUM_VALUE(a)) * MEDIUM_VALUE(b); 614 | return PyLong_FromLongLong((long long)v); 615 | } 616 | 617 | z = k_mul(a, b); 618 | /* Negate if exactly one of the inputs is negative. */ 619 | if (((Py_SIZE(a) ^ Py_SIZE(b)) < 0) && z) { 620 | _PyLong_Negate(&z); 621 | if (z == NULL) 622 | return NULL; 623 | } 624 | return (PyObject *)z; 625 | } 626 | ``` 627 | 628 | k_mul 函数是一种快速乘法 [源文件](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L3268) 629 | 630 | > Karatsuba 的算法主要是用于两个大数的乘法,极大提高了运算效率,相较于普通乘法降低了复杂度,并在其中运用了递归的思想。 631 | > 基本的原理和做法是将位数很多的两个大数 x 和 y 分成位数较少的数,每个数都是原来 x 和 y 位数的一半。 632 | > 这样处理之后,简化为做三次乘法,并附带少量的加法操作和移位操作。 633 | 634 | 具体可以看 wiki [Karatsuba 算法](https://www.wikiwand.com/zh-hans/Karatsuba算法)的实现 635 | -------------------------------------------------------------------------------- /objects/long-object/long-storage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/long-object/long-storage.png -------------------------------------------------------------------------------- /objects/long-object/long-x-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/long-object/long-x-add.png -------------------------------------------------------------------------------- /objects/long-object/long-x-sub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/long-object/long-x-sub.png -------------------------------------------------------------------------------- /objects/object/PyObject.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/object/PyObject.jpg -------------------------------------------------------------------------------- /objects/object/PyVarObject.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/object/PyVarObject.jpg -------------------------------------------------------------------------------- /objects/object/index.md: -------------------------------------------------------------------------------- 1 | # Python 对象初探 2 | 3 | 在 Python 的世界一切皆对象,不论是整数,还是字符串,甚至连类型、函数等都是一种对象。 4 | 5 | ## 对象的分类 6 | 7 | 以下是 Python 对象的大致的一个分类 8 | 9 | - Fundamental 对象: 类型对象 10 | - Numeric 对象: 数值对象 11 | - Sequence 对象: 容纳其他对象的序列集合对象 12 | - Mapping 对象: 类似 C++中的 map 的关联对象 13 | - Internal 对象: Python 虚拟机在运行时内部使用的对象 14 | 15 | ![object category](object-category.jpg) 16 | 17 | ## 对象机制的基石 PyObject 18 | 19 | 对于初学者来说这么多类型的对象怎么学?别着急,我们后续章节会解答。 20 | 21 | 在开始我们的学习之旅之前,我们要先认识一个结构体**PyObject**,可以说 Python 的对象机制就是基于**PyObject**拓展开来的,所以我们先看看**PyObject** 到底长什么样。 22 | 23 | `源文件:`[Include/object.h](https://github.com/python/cpython/blob/v3.7.0/Include/object.h#L106) 24 | 25 | ```c 26 | // Include/object.h 27 | #define _PyObject_HEAD_EXTRA \ 28 | struct _object *_ob_next; \ 29 | struct _object *_ob_prev; 30 | 31 | typedef struct _object { 32 | _PyObject_HEAD_EXTRA // 双向链表 垃圾回收 需要用到 33 | Py_ssize_t ob_refcnt; // 引用计数 34 | struct _typeobject *ob_type; // 指向类型对象的指针,决定了对象的类型 35 | } PyObject; 36 | ``` 37 | 38 | Python 中的所有对象都拥有一些相同的内容,而这些内容就定义在**PyObject**中, 39 | 40 | **PyObject** 包含 一个用于垃圾回收的双向链表,一个引用计数变量 `ob_refcnt` 和 一个类型对象指针`ob_type` 41 | 42 | ![PyObject](PyObject.jpg) 43 | 44 | ## 定长对象与变长对象 45 | 46 | Python 对象除了前面提到的那种分类方法外,还可以分为定长对象和变长对象这两种形式。 47 | 48 | 变长对象都拥有一个相同的内容 **PyVarObject**,而 **PyVarObject**也是基于**PyObject**扩展的。 49 | 50 | 从代码中可以看出**PyVarObject**比**PyObject**多出了一个用于存储元素个数的变量*ob_size*。 51 | 52 | `源文件:`[Include/object.h](https://github.com/python/cpython/blob/v3.7.0/Include/object.h#L106) 53 | 54 | ```c 55 | // Include/object.h 56 | typedef struct _object { 57 | _PyObject_HEAD_EXTRA 58 | Py_ssize_t ob_refcnt; 59 | struct _typeobject *ob_type; 60 | } PyObject; 61 | 62 | typedef struct { 63 | PyObject ob_base; 64 | Py_ssize_t ob_size; /* Number of items in variable part */ 65 | } PyVarObject; 66 | ``` 67 | 68 | ![PyVarObject](PyVarObject.jpg) 69 | 70 | ## 类型对象 71 | 72 | 前面我们提到了**PyObject** 的 对象类型指针`struct _typeobject *ob_type`,它指向的类型对象就决定了一个对象是什么类型的。 73 | 74 | 这是一个非常重要的结构体,它不仅仅决定了一个对象的类型,还包含大量的`元信息`, 75 | 包括创建对象需要分配多少内存,对象都支持哪些操作等等。 76 | 77 | 接下来我们看一下`struct _typeobject`代码 78 | 79 | 在 **PyTypeObject** 的定义中包含许多信息,主要分类以下几类: 80 | 81 | - 类型名, tp_name, 主要用于 Python 内部调试用 82 | - 创建该类型对象时分配的空间大小信息,即 `tp_basicsize` 和 `tp_itemsize` 83 | - 与该类型对象相关的操作信息(如 `tp_print` 这样的函数指针) 84 | - 一些对象属性 85 | 86 | `源文件:`[Include/object.h](https://github.com/python/cpython/blob/v3.7.0/Include/object.h#L346) 87 | 88 | ```c 89 | // Include/object.h 90 | typedef struct _typeobject { 91 | PyObject_VAR_HEAD 92 | const char *tp_name; /* For printing, in format "." */ // 类型名 93 | Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */ 94 | // 创建该类型对象分配的内存空间大小 95 | 96 | // 一堆方法定义,函数和指针 97 | /* Methods to implement standard operations */ 98 | destructor tp_dealloc; 99 | printfunc tp_print; 100 | getattrfunc tp_getattr; 101 | setattrfunc tp_setattr; 102 | PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2) 103 | or tp_reserved (Python 3) */ 104 | reprfunc tp_repr; 105 | 106 | /* Method suites for standard classes */ 107 | // 标准类方法集 108 | PyNumberMethods *tp_as_number; // 数值对象操作 109 | PySequenceMethods *tp_as_sequence; // 序列对象操作 110 | PyMappingMethods *tp_as_mapping; // 字典对象操作 111 | 112 | // 更多标准操作 113 | /* More standard operations (here for binary compatibility) */ 114 | hashfunc tp_hash; 115 | ternaryfunc tp_call; 116 | reprfunc tp_str; 117 | getattrofunc tp_getattro; 118 | setattrofunc tp_setattro; 119 | 120 | ...... 121 | 122 | } PyTypeObject; 123 | ``` 124 | 125 | ## 类型的类型 126 | 127 | 在 **PyTypeObjet** 定义开始有一个宏`PyOject_VAR_HEAD`,查看源码可知 **PyTypeObjet** 是一个变长对象 128 | 129 | `源文件:`[Include/object.h](https://github.com/python/cpython/blob/v3.7.0/Include/object.h#L98) 130 | 131 | ```c 132 | // Include/object.h 133 | #define PyObject_VAR_HEAD PyVarObject ob_base; 134 | ``` 135 | 136 | 对象的类型是由该对象指向的 类型对象 决定的,那么类型对象的类型是由谁决定的呢? 137 | 对于其他对象,可以通过与其关联的类型对象确定其类型,那么通过什么来确定一个对象是类型对象呢? 138 | 答案就是 `PyType_Type` 139 | 140 | `源文件:`[Objects/typeobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/typeobject.c#L3540) 141 | 142 | ```c 143 | // Objects/typeobject.c 144 | PyTypeObject PyType_Type = { 145 | PyVarObject_HEAD_INIT(&PyType_Type, 0) 146 | "type", /* tp_name */ 147 | sizeof(PyHeapTypeObject), /* tp_basicsize */ 148 | sizeof(PyMemberDef), /* tp_itemsize */ 149 | 150 | ...... 151 | }; 152 | ``` 153 | 154 | `PyType_Type` 在类型机制中至关重要,所有用户自定义 `class` 所 155 | 对应的 `PyTypeObject` 对象都是通过 `PyType_Type`创建的 156 | 157 | 接下来我们看 `PyLong_Type` 是怎么与 `PyType_Type` 建立联系的。 158 | 前面提到,在 Python 中,每一个对象都将自己的引用计数、类型信息保存在开始的部分中。 159 | 为了方便对这部分内存初始化,Python 中提供了几个有用的宏: 160 | 161 | `源文件:`[Include/object.h](https://github.com/python/cpython/blob/v3.7.0/Include/object.h#L69) 162 | 163 | ```c 164 | // Include/object.h 165 | #ifdef Py_TRACE_REFS 166 | #define _PyObject_EXTRA_INIT 0, 0, 167 | #else 168 | #define _PyObject_EXTRA_INIT 169 | #endif 170 | 171 | #define PyObject_HEAD_INIT(type) \ 172 | { _PyObject_EXTRA_INIT \ 173 | 1, type }, 174 | ``` 175 | 176 | 这些宏在各种内建类型对象的初始化中被大量使用。 177 | 以`PyLong_Type`为例,可以清晰的看到一般的类型对象和`PyType_Type`之间的关系 178 | 179 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L5379) 180 | 181 | ```c 182 | // Objects/longobject.c 183 | 184 | PyTypeObject PyLong_Type = { 185 | PyVarObject_HEAD_INIT(&PyType_Type, 0) 186 | "int", /* tp_name */ 187 | offsetof(PyLongObject, ob_digit), /* tp_basicsize */ 188 | sizeof(digit), /* tp_itemsize */ 189 | 190 | ...... 191 | }; 192 | ``` 193 | 194 | 下图是对象运行时的图像表现 195 | 196 | ![](object-runtime-relation.jpg) 197 | 198 | ## 对象的创建 199 | 200 | Python 创建对象有两种方式 201 | 202 | ### 范型 API 或称为 AOL (Abstract Object Layer) 203 | 204 | 这类 API 通常形如`PyObject_XXX`这样的形式。可以应用在任何 Python 对象上, 205 | 如`PyObject_New`。创建一个整数对象的方式 206 | 207 | ```c 208 | PyObject* longobj = PyObject_New(Pyobject, &PyLong_Type); 209 | ``` 210 | 211 | ### 与类型相关的 API 或称为 COL (Concrete Object Layer) 212 | 213 | 这类 API 通常只能作用于某一种类型的对象上,对于每一种内建对象 214 | Python 都提供了这样一组 API。例如整数对象,我们可以利用如下的 API 创建 215 | 216 | ```c 217 | PyObject *longObj = PyLong_FromLong(10); 218 | ``` 219 | 220 | ## 对象的行为 221 | 222 | 在 **PyTypeObject** 中定义了大量的函数指针。这些函数指针可以视为类型对象中 223 | 所定义的操作,这些操作直接决定着一个对象在运行时所表现出的行为,比如 **PyTypeObject** 中的 `tp_hash` 指明了该类型对象如何生成其`hash`值。 224 | 225 | 在**PyTypeObject**的代码中,我们还可以看到非常重要的三组操作族 226 | 227 | - `PyNumberMethods *tp_as_number` 228 | - `PySequenceMethods *tp_as_sequence` 229 | - `PyMappingMethods *tp_as_mapping` 230 | 231 | **PyNumberMethods** 的代码如下 232 | 233 | `源文件:`[Include/object.h](https://github.com/python/cpython/blob/v3.7.0/Include/object.h#L240) 234 | 235 | ```c 236 | // Include/object.h 237 | typedef PyObject * (*binaryfunc)(PyObject *, PyObject *); 238 | 239 | typedef struct { 240 | binaryfunc nb_matrix_multiply; 241 | binaryfunc nb_inplace_matrix_multiply; 242 | 243 | ...... 244 | } PyNumberMethods; 245 | ``` 246 | 247 | **PyNumberMethods** 定义了一个数值对象该支持的操作。一个数值对象如 整数对象,那么它的类型对象 `PyLong_Type`中`tp_as_number.nb_add` 248 | 就指定了它进行加法操作时的具体行为。 249 | 250 | 在以下代码中可以看出`PyLong_Type`中的`tp_as_number`项指向的是`long_as_number` 251 | 252 | `源文件:`[Objects/longobject.h](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L5342) 253 | 254 | ```c 255 | // Objects/longobject.c 256 | static PyNumberMethods long_as_number = { 257 | (binaryfunc)long_add, /*nb_add*/ 258 | (binaryfunc)long_sub, /*nb_subtract*/ 259 | (binaryfunc)long_mul, /*nb_multiply*/ 260 | 261 | ...... 262 | }; 263 | 264 | PyTypeObject PyLong_Type = { 265 | PyVarObject_HEAD_INIT(&PyType_Type, 0) 266 | "int", /* tp_name */ 267 | offsetof(PyLongObject, ob_digit), /* tp_basicsize */ 268 | sizeof(digit), /* tp_itemsize */ 269 | long_dealloc, /* tp_dealloc */ 270 | 0, /* tp_print */ 271 | 0, /* tp_getattr */ 272 | 0, /* tp_setattr */ 273 | 0, /* tp_reserved */ 274 | long_to_decimal_string, /* tp_repr */ 275 | &long_as_number, /* tp_as_number */ 276 | 0, /* tp_as_sequence */ 277 | 0, /* tp_as_mapping */ 278 | 279 | ...... 280 | }; 281 | ``` 282 | 283 | `PySequenceMethods *tp_as_sequence` 和 `PyMappingMethods *tp_as_mapping`的分析与`PyNumberMethods *tp_as_number` 相同,大家可以自行查阅源码 284 | 285 | ## 对象的多态性 286 | 287 | Python 创建一个对象比如 **PyLongObject** 时,会分配内存进行初始化,然后 288 | Python 内部会用 `PyObject*` 变量来维护这个对象,其他对象也与此类似 289 | 290 | 所以在 Python 内部各个函数之间传递的都是一种范型指针 `PyObject*` 291 | 我们不知道这个指针所指的对象是什么类型,只能通过所指对象的 `ob_type` 域 292 | 动态进行判断,而 Python 正是通过 `ob_type` 实现了多态机制 293 | 294 | 考虑以下的 calc_hash 函数 295 | 296 | ```c 297 | Py_hash_t 298 | calc_hash(PyObject* object) 299 | { 300 | Py_hash_t hash = object->ob_type->tp_hash(object); 301 | return hash; 302 | } 303 | ``` 304 | 305 | 如果传递给 calc_hash 函数的指针是一个 `PyLongObject*`,那么它会调用 PyLongObject 对象对应的类型对象中定义的 hash 操作`tp_hash`,`tp_hash`可以在**PyTypeObject**中找到, 306 | 而具体赋值绑定我们可以在 `PyLong_Type` 初始化代码中看到绑定的是`long_hash`函数 307 | 308 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L5379) 309 | 310 | ```c 311 | // Objects/longobject.c 312 | PyTypeObject PyLong_Type = { 313 | PyVarObject_HEAD_INIT(&PyType_Type, 0) 314 | "int", /* tp_name */ 315 | ... 316 | 317 | (hashfunc)long_hash, /* tp_hash */ 318 | 319 | ... 320 | }; 321 | ``` 322 | 323 | 如果指针是一个 `PyUnicodeObject*`,那么就会调用 PyUnicodeObject 对象对应的类型对象中定义的 hash 操作,查看源码可以看到 实际绑定的是 `unicode_hash`函数 324 | 325 | `源文件:`[Objects/unicodeobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/unicodeobject.c#L15066) 326 | 327 | ```c 328 | // Objects/unicodeobject.c 329 | PyTypeObject PyUnicode_Type = { 330 | PyVarObject_HEAD_INIT(&PyType_Type, 0) 331 | "str", /* tp_name */ 332 | 333 | ... 334 | 335 | (hashfunc) unicode_hash, /* tp_hash*/ 336 | 337 | ... 338 | }; 339 | ``` 340 | 341 | ## 引用计数 342 | 343 | Python 通过引用计数来管理维护对象在内存中的存在与否 344 | 345 | Python 中的每个东西都是一个对象, 都有`ob_refcnt` 变量,这个变量维护对象的引用计数,从而最终决定该对象的创建与销毁 346 | 347 | 在 Python 中,主要通过 `Py_INCREF(op)`与`Py_DECREF(op)` 这两个宏 348 | 来增加和减少对一个对象的引用计数。当一个对象的引用计数减少到 0 之后, 349 | `Py_DECREF`将调用该对象的`tp_dealloc`来释放对象所占用的内存和系统资源; 350 | 351 | 但这并不意味着最终一定会调用 `free` 释放内存空间。因为频繁的申请、释放内存会大大降低 Python 的执行效率。因此 Python 中大量采用了内存对象池的技术,使得对象释放的空间归还给内存池而不是直接`free`,后续使用可先从对象池中获取 352 | 353 | `源文件:`[Include/object.h](https://github.com/python/cpython/blob/v3.7.0/Include/object.h#L777) 354 | 355 | ```c 356 | // Include/object.h 357 | #define _Py_NewReference(op) ( \ 358 | _Py_INC_TPALLOCS(op) _Py_COUNT_ALLOCS_COMMA \ 359 | _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \ 360 | Py_REFCNT(op) = 1) 361 | 362 | #define Py_INCREF(op) ( \ 363 | _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \ 364 | ((PyObject *)(op))->ob_refcnt++) 365 | 366 | #define Py_DECREF(op) \ 367 | do { \ 368 | PyObject *_py_decref_tmp = (PyObject *)(op); \ 369 | if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \ 370 | --(_py_decref_tmp)->ob_refcnt != 0) \ 371 | _Py_CHECK_REFCNT(_py_decref_tmp) \ 372 | else \ 373 | _Py_Dealloc(_py_decref_tmp); \ 374 | } while (0) 375 | ``` 376 | -------------------------------------------------------------------------------- /objects/object/object-category.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/object/object-category.jpg -------------------------------------------------------------------------------- /objects/object/object-runtime-relation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/object/object-runtime-relation.jpg -------------------------------------------------------------------------------- /objects/set-object/index.md: -------------------------------------------------------------------------------- 1 | # python 集合 2 | 3 | set 是无序且不重复的集合,是可变的,通常用来从列表中删除重复项以及计算数学运算,如交集、并集、差分和对称差分等集合操作。set 支持 x in set, len(set),和 for x in set。作为一个无序的集合,set 不记录元素位置或者插入点。因此,sets 不支持 indexing, 或其它类序列的操作。 4 | 5 | ## python 集合概述 6 | 7 | 在 set 中,对应的 set 的值的存储是通过结构 setentry 来保存数据值的; 8 | 9 | `源文件:`[include/setobject.h](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Include/setobject.h#L26) 10 | 11 | ```c 12 | typedef struct { 13 | PyObject *key; 14 | Py_hash_t hash; /* Cached hash code of the key */ 15 | } setentry; 16 | ``` 17 | 18 | key 就是保存的数据,hash 就是保存的数据的 hash,便于查找,set 也是基于 hash 表来实现。对应的 setentry 所对应的 set 的数据结构如下; 19 | 20 | `源文件:`[include/setobject.h](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Include/setobject.h#L42) 21 | 22 | ```c 23 | typedef struct { 24 | PyObject_HEAD 25 | 26 | Py_ssize_t fill; /* Number active and dummy entries*/ // 包括已经使用的entry与空entry值的总和 27 | Py_ssize_t used; /* Number active entries */ // 已经使用可用的总量 28 | 29 | /* The table contains mask + 1 slots, and that's a power of 2. 30 | * We store the mask instead of the size because the mask is more 31 | * frequently needed. 32 | */ 33 | Py_ssize_t mask;                                // 与hash求和的mask 34 | 35 | /* The table points to a fixed-size smalltable for small tables 36 | * or to additional malloc'ed memory for bigger tables. 37 | * The table pointer is never NULL which saves us from repeated 38 | * runtime null-tests. 39 | */ 40 | setentry *table; // 保存数据的数组数组指针 41 | Py_hash_t hash; /* Only used by frozenset objects */ 42 | Py_ssize_t finger; /* Search finger for pop() */ 43 | 44 | setentry smalltable[PySet_MINSIZE]; // 保存数据的数组 默认初始化为8个元素,通过table指向 45 | PyObject *weakreflist; /* List of weak references */ 46 | } PySetObject; 47 | ``` 48 | 49 | 一个 set 就对应一个 PySetObject 类型数据,set 会根据保存的元素自动调整大小。相关的内存布局如下; 50 | 51 | ![内存图片](set.png) 52 | 53 | ## python 集合(set)示例 54 | 55 | 示例脚本如下: 56 | 57 | ```python 58 | set_a = {1,2}  59 | set_a.add(3) 60 | set_a.add(4) 61 | set_a.remove(1) 62 | set_a.update({3,}) 63 | set_a.union({1,5}) 64 | ``` 65 | 66 | 通过 python 反汇编获取该脚本的字节码; 67 | 68 | ``` 69 | python -m dis set_test.py 70 | ``` 71 | 72 | 输出的字节码如下所示; 73 | 74 | ```shell 75 | 1 0 LOAD_CONST 0 (1) 76 | 3 LOAD_CONST 1 (2) 77 | 6 BUILD_SET 2 78 | 9 STORE_NAME 0 (set_a) 79 | 80 | 2 12 LOAD_NAME 0 (set_a) 81 | 15 LOAD_ATTR 1 (add) 82 | 18 LOAD_CONST 2 (3) 83 | 21 CALL_FUNCTION 1 84 | 24 POP_TOP 85 | 86 | 3 25 LOAD_NAME 0 (set_a) 87 | 28 LOAD_ATTR 1 (add) 88 | 31 LOAD_CONST 3 (4) 89 | 34 CALL_FUNCTION 1 90 | 37 POP_TOP 91 | 92 | 4 38 LOAD_NAME 0 (set_a) 93 | 41 LOAD_ATTR 2 (remove) 94 | 44 LOAD_CONST 0 (1) 95 | 47 CALL_FUNCTION 1 96 | 50 POP_TOP 97 | 98 | 5 51 LOAD_NAME 0 (set_a) 99 | 54 LOAD_ATTR 3 (update) 100 | 57 LOAD_CONST 2 (3) 101 | 60 BUILD_SET 1 102 | 63 CALL_FUNCTION 1 103 | 66 POP_TOP 104 | 105 | 6 67 LOAD_NAME 0 (set_a) 106 | 70 LOAD_ATTR 4 (union) 107 | 73 LOAD_CONST 0 (1) 108 | 76 LOAD_CONST 4 (5) 109 | 79 BUILD_SET 2 110 | 82 CALL_FUNCTION 1 111 | 85 POP_TOP 112 | 86 LOAD_CONST 5 (None) 113 | 89 RETURN_VALUE 114 | ``` 115 | 116 | 通过该字节码指令可知,创建 set 调用了 BUILD_SET 指令,初始化完成之后,就调用 set 的 add 方法添加元素,调用 remove 删除元素,调用 update 来更新集合,通过 union 来合并集合。接下来就详细分析一下相关的操作流程。 117 | 118 | ## set 的创建与初始化 119 | 120 | 查找 BUILD_SET 的虚拟机执行函数如下; 121 | 122 | `源文件:`[Python/ceval.c](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Python/ceval.c#L2318) 123 | 124 | ```c 125 | // Python/ceval.c 126 | 127 | TARGET(BUILD_SET) { 128 | PyObject *set = PySet_New(NULL); // 新建并初始化一个set 129 | int err = 0; 130 | int i; 131 | if (set == NULL) 132 | goto error; 133 | for (i = oparg; i > 0; i--) { // 将传入初始化的参数传入 134 | PyObject *item = PEEK(i); 135 | if (err == 0) 136 | err = PySet_Add(set, item); // 并依次对set进行添加操作 137 | Py_DECREF(item); 138 | } 139 | STACKADJ(-oparg);                // 移动弹栈 140 | if (err != 0) { 141 | Py_DECREF(set); 142 | goto error; 143 | } 144 | PUSH(set);                     // 讲set压栈 145 | DISPATCH();                    // 执行下一条指令 146 | } 147 | 148 | ``` 149 | 150 | 此时继续查看 PySet_New 函数的执行流程; 151 | 152 | `源文件:`[Objects/setobject.c](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Objects/setobject.c#L2286) 153 | 154 | ```c 155 | PyObject * 156 | PySet_New(PyObject *iterable) 157 | { 158 | return make_new_set(&PySet_Type, iterable); 159 | } 160 | 161 | ... 162 | 163 | 164 | static PyObject * 165 | make_new_set(PyTypeObject *type, PyObject *iterable) 166 | { 167 | PySetObject *so; 168 | 169 | so = (PySetObject *)type->tp_alloc(type, 0); // 申请该元素的内存 170 | if (so == NULL) // 内存申请失败则返回为空 171 | return NULL; 172 | 173 | so->fill = 0; // 初始化的时候都为0 174 | so->used = 0; 175 | so->mask = PySet_MINSIZE - 1; // PySet_MINSIZE默认我8,mask为7 176 | so->table = so->smalltable; // 将保存数据的头指针指向table 177 | so->hash = -1; // 设置hash值为-1 178 | so->finger = 0; 179 | so->weakreflist = NULL; 180 | 181 | if (iterable != NULL) { // 如果有迭代器 182 | if (set_update_internal(so, iterable)) { // 将内容更新到so中 183 | Py_DECREF(so); 184 | return NULL; 185 | } 186 | } 187 | 188 | return (PyObject *)so; // 返回初始化完成的set 189 | } 190 | ``` 191 | 192 | 从 PySet_New 的执行流程可知,字典的初始化过程就是初始化相关数据结构。 193 | 194 | ## set 的插入 195 | 196 | 在本例的初始化过程中,由于传入了初始值 1,2,所以会在执行字节码指令的时候,执行 PySet_Add,该函数的本质与 set_a.add(3)本质都调用了更底层 set_add_key 函数; 197 | 198 | `源文件:`[Objects/setobject.c](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Objects/setobject.c#L2338) 199 | 200 | ```c 201 | 202 | int 203 | PySet_Add(PyObject *anyset, PyObject *key) 204 | { 205 | if (!PySet_Check(anyset) && 206 | (!PyFrozenSet_Check(anyset) || Py_REFCNT(anyset) != 1)) { 207 | PyErr_BadInternalCall(); 208 | return -1; 209 | } 210 | return set_add_key((PySetObject *)anyset, key); // 向字典中添加key; 211 | } 212 | ``` 213 | 214 | 继续查看 set_add_key 函数的执行过程; 215 | 216 | `源文件:`[Objects/setobject.c](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Objects/setobject.c#L419) 217 | 218 | ```c 219 | static int 220 | set_add_key(PySetObject *so, PyObject *key) 221 | { 222 | Py_hash_t hash; 223 | 224 | if (!PyUnicode_CheckExact(key) || 225 | (hash = ((PyASCIIObject *) key)->hash) == -1) { 226 | hash = PyObject_Hash(key); // 获取传入值的hash值 227 | if (hash == -1) // 如果不能hash则返回-1 228 | return -1; 229 | } 230 | return set_add_entry(so, key, hash); // 计算完成后添加值 231 | } 232 | ``` 233 | 234 | 该函数主要就是检查传入的 key 是否能够被 hash,如果能够被 hash 则直接返回,如果能被 hash 则继续调用 set_add_entry 函数将值加入到 set 中; 235 | 236 | `源文件:`[Objects/setobject.c](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Objects/setobject.c#L136) 237 | 238 | ```c 239 | 240 | static int 241 | set_add_entry(PySetObject *so, PyObject *key, Py_hash_t hash) 242 | { 243 | setentry *table; 244 | setentry *freeslot; 245 | setentry *entry; 246 | size_t perturb; 247 | size_t mask; 248 | size_t i; /* Unsigned for defined overflow behavior */ 249 | size_t j; 250 | int cmp; 251 | 252 | /* Pre-increment is necessary to prevent arbitrary code in the rich 253 | comparison from deallocating the key just before the insertion. */ 254 | Py_INCREF(key); // 提高key的引用计数 255 | 256 | restart: 257 | 258 | mask = so->mask;  // 获取so->mask 259 | i = (size_t)hash & mask;  // 通过传入的hash与mask求索引下标 260 | 261 | entry = &so->table[i];    // 获取索引对应的值 262 | if (entry->key == NULL) // 如果获取索引的值没有被使用则直接跳转到found_unused处执行 263 | goto found_unused; 264 | 265 | freeslot = NULL; 266 | perturb = hash;    // perturb设置为当前hash值 267 |   268 | while (1) { 269 | if (entry->hash == hash) { // 如果当前hash值相等 270 | PyObject *startkey = entry->key;                      // 获取当前key 271 | /* startkey cannot be a dummy because the dummy hash field is -1 */ 272 | assert(startkey != dummy); // 检查key是否为dummy 273 | if (startkey == key) // 如果找到的值与传入需要设置的值相同则跳转到found_active处执行 274 | goto found_active; 275 | if (PyUnicode_CheckExact(startkey) 276 | && PyUnicode_CheckExact(key) 277 | && _PyUnicode_EQ(startkey, key)) // 如果是unicode,通过类型转换检查两个key的内容是否相同,如果不相同则跳转到found_active处 278 | goto found_active; 279 | table = so->table; // 如果没有找到,则获取当前table的头部节点 280 | Py_INCREF(startkey); 281 | cmp = PyObject_RichCompareBool(startkey, key, Py_EQ);          // 如果是其他类型的对象则调用比较方法去比较两个key是否相同 282 | Py_DECREF(startkey); 283 | if (cmp > 0) /* likely */ // 如果找到则跳转到found_active 284 | goto found_active; 285 | if (cmp < 0) 286 | goto comparison_error; // 如果小于0,则是两个类型对比失败 287 | /* Continuing the search from the current entry only makes 288 | sense if the table and entry are unchanged; otherwise, 289 | we have to restart from the beginning */ 290 | if (table != so->table || entry->key != startkey) // 如果set改变了则重新开始查找 291 | goto restart; 292 | mask = so->mask; /* help avoid a register spill */    293 | } 294 | else if (entry->hash == -1) 295 | freeslot = entry;    // 如果不能hash 则设置freeslot 296 | 297 | if (i + LINEAR_PROBES <= mask) {               // 检查当前索引值加上 9小于当前mask 298 | for (j = 0 ; j < LINEAR_PROBES ; j++) { // 循环9次 299 | entry++;     // 向下一个位置 300 | if (entry->hash == 0 && entry->key == NULL)              // 如果找到当前hash为空或者key为空的则跳转到found_unused_or_dummy处执行 301 | goto found_unused_or_dummy; 302 | if (entry->hash == hash) {   // 如果找到的hash值相同 303 | PyObject *startkey = entry->key; // 获取该值 304 | assert(startkey != dummy); // 检查是否为dummy 305 | if (startkey == key) // 如果key相同则跳转到found_active处执行 306 | goto found_active; 307 | if (PyUnicode_CheckExact(startkey) 308 | && PyUnicode_CheckExact(key) 309 | && _PyUnicode_EQ(startkey, key)) // 检查是否为unicode,并比较如果不相同则跳转到found_active 310 | goto found_active; 311 | table = so->table; // 调用key本身的方法比较 312 | Py_INCREF(startkey); 313 | cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); 314 | Py_DECREF(startkey); 315 | if (cmp > 0) 316 | goto found_active; 317 | if (cmp < 0) 318 | goto comparison_error; 319 | if (table != so->table || entry->key != startkey) 320 | goto restart; 321 | mask = so->mask; 322 | } 323 | else if (entry->hash == -1) 324 | freeslot = entry; 325 | } 326 | } 327 | 328 | perturb >>= PERTURB_SHIFT; // 如果没有找到则获取下一个索引值 329 | i = (i * 5 + 1 + perturb) & mask; // 右移5位 加上 索引值*5 加1与mask求余获取下一个索引值 330 | 331 | entry = &so->table[i]; // 获取下一个元素 332 | if (entry->key == NULL)               // 如果找到为空则直接跳转到found_unused_or_dummy处 333 | goto found_unused_or_dummy; 334 | } 335 | 336 | found_unused_or_dummy: 337 | if (freeslot == NULL)                                  // 检查freeslot是否为空如果为空则跳转到found_unused处执行即找到了dummy位置 338 | goto found_unused; 339 | so->used++;                       // 使用数加1 340 | freeslot->key = key;                                   // 设置key与hash值 341 | freeslot->hash = hash; 342 | return 0; 343 | 344 | found_unused: 345 | so->fill++;                                        // 使用总数加1 346 | so->used++;                                        // 使用总数加1  347 | entry->key = key;                                     // 设置key与hash值 348 | entry->hash = hash; 349 | if ((size_t)so->fill*5 < mask*3)                           // 检查已经使用的值是否是总数的3/5 350 | return 0; 351 | return set_table_resize(so, so->used>50000 ? so->used*2 : so->used*4);    // 如果已使用的总数大于3/5则重新调整table,如果set使用的总数超过了50000则扩展为以前的2倍否则就是四倍 352 | 353 | found_active: 354 | Py_DECREF(key);                                      // 如果找到了该值 则什么也不做 355 | return 0; 356 | 357 | comparison_error: 358 | Py_DECREF(key);                                      // 如果比较失败则返回-1 359 | return -1; 360 | } 361 | ``` 362 | 363 | 此时基本的流程就是通过传入的 hash 值,如果计算出的索引值,没有值,则直接将该值存入对应的 entry 中,如果相同则不插入,如果索引对应的值且值不同,则遍历从该索引往后9个位置的值,依次找到有空余位置的值,并将该值设置进去。如果设置该值之后使用的数量占总的申请数量超过了 3/5 则重新扩充 set,扩充的原则就是如果当前的 set->used>50000 就进行两倍扩充否则就进行四倍扩充。 364 | 365 | 插入的概述如下,默认 s 初始化为空; 366 | 367 | ```python 368 | s.add(1) // index = 1 & 7 = 1 369 | ``` 370 | 371 | ![插入1](set-insert-one.png) 372 | 373 | ```python 374 | s.add(2) // index = 2 & 7 = 2 375 | ``` 376 | 377 | ![插入2](set-insert-two.png) 378 | 379 | ```python 380 | s.add(7) // index = 9 & 7 = 1 381 | ``` 382 | 383 | ![插入9](set-insert-nine.png) 384 | 385 | 大致的 set 的插入过程执行完毕。 386 | 387 | ## set 的删除 388 | 389 | set 的删除操作主要集中在 set_remove()函数上,如下示例; 390 | 391 | `源文件:`[Objects/setobject.c](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Objects/setobject.c#L1921) 392 | 393 | ```c 394 | 395 | static PyObject * 396 | set_remove(PySetObject *so, PyObject *key) 397 | { 398 | PyObject *tmpkey; 399 | int rv; 400 | 401 | rv = set_discard_key(so, key); // 将该key设置为dummy 402 | if (rv < 0) { 403 | if (!PySet_Check(key) || !PyErr_ExceptionMatches(PyExc_TypeError)) // 检查是否为set类型 404 | return NULL; 405 | PyErr_Clear(); 406 | tmpkey = make_new_set(&PyFrozenSet_Type, key);             // 对该值重新初始化为forzenset 407 | if (tmpkey == NULL) 408 | return NULL; 409 | rv = set_discard_key(so, tmpkey);                     // 设置该key为空 410 | Py_DECREF(tmpkey); 411 | if (rv < 0) 412 | return NULL; 413 | } 414 | 415 | if (rv == DISCARD_NOTFOUND) { // 如果没有找到则报错 416 | _PyErr_SetKeyError(key); 417 | return NULL; 418 | } 419 | Py_RETURN_NONE; 420 | } 421 | ``` 422 | 423 | 此时就会调用 set_discard_key 方法来讲对应的 entry 设置为 dummy;set_discard_key 方法如下; 424 | 425 | `源文件:`[Objects/setobject.c](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Objects/setobject.c#L447) 426 | 427 | ```c 428 | 429 | static int 430 | set_discard_key(PySetObject *so, PyObject *key) 431 | { 432 | Py_hash_t hash; 433 | 434 | if (!PyUnicode_CheckExact(key) || 435 | (hash = ((PyASCIIObject *) key)->hash) == -1) { 436 | hash = PyObject_Hash(key);  // 检查是否可用hash如果可用则调用set_discard_entry方法 437 | if (hash == -1) 438 | return -1; 439 | } 440 | return set_discard_entry(so, key, hash); 441 | } 442 | ``` 443 | 444 | 该函数主要就是做了检查 key 是否可用 hash 的检查,此时如果可用 hash 则调用 set_discard_entry 方法; 445 | 446 | `源文件:`[Objects/setobject.c](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Objects/setobject.c#L400) 447 | 448 | ```c 449 | 450 | static int 451 | set_discard_entry(PySetObject *so, PyObject *key, Py_hash_t hash) 452 | { 453 | setentry *entry; 454 | PyObject *old_key; 455 | 456 | entry = set_lookkey(so, key, hash);      // 查找该值 set_lookkey该方法与插入的逻辑类似大家可自行查看 457 | if (entry == NULL)                 // 如果没有找到则返回-1 458 | return -1; 459 | if (entry->key == NULL) 460 | return DISCARD_NOTFOUND;           // 找到entry而key为空则返回notfound 461 | old_key = entry->key; // 找到正常值则讲该值对应的entry设置为dummy 462 | entry->key = dummy; 463 | entry->hash = -1; // hash值为-1 464 | so->used--; // 使用数量减1 但是fill数量未变 465 | Py_DECREF(old_key);                 // 减少该对象引用 466 | return DISCARD_FOUND;                // 返回返现 467 | } 468 | ``` 469 | 470 | 此时就是查找该值,如果找到该值并将该值设置为 dummy,并且将 used 值减1,此处没有减去 fill 的数量,从此处可知,fill 包括所有曾经申请过的数量。 471 | 472 | ## set 的 resize 473 | 474 | set 的 resize 主要依靠 set_table_reseize 函数来实现; 475 | 476 | `源文件:`[Objects/setobject.c](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Objects/setobject.c#L302) 477 | 478 | ```c 479 | static int 480 | set_table_resize(PySetObject *so, Py_ssize_t minused) 481 | { 482 | setentry *oldtable, *newtable, *entry; 483 | Py_ssize_t oldmask = so->mask; // 设置旧的mask 484 | size_t newmask; 485 | int is_oldtable_malloced; 486 | setentry small_copy[PySet_MINSIZE]; // 最小的拷贝数组 487 | 488 | assert(minused >= 0); 489 | 490 | /* Find the smallest table size > minused. */ 491 | /* XXX speed-up with intrinsics */ 492 | size_t newsize = PySet_MINSIZE; 493 | while (newsize <= (size_t)minused) { 494 | newsize <<= 1; // The largest possible value is PY_SSIZE_T_MAX + 1.  // 查找位于minused最大的PySet_MINSIZE的n次方的值 495 | } 496 | 497 | /* Get space for a new table. */ 498 | oldtable = so->table;                   // 先获取旧的table 499 | assert(oldtable != NULL); 500 | is_oldtable_malloced = oldtable != so->smalltable; 501 | 502 | if (newsize == PySet_MINSIZE) {                  // 如果获取的新大小与PySet_MINSIZE的大小相同 503 | /* A large table is shrinking, or we can't get any smaller. */ 504 | newtable = so->smalltable;                  // 获取新table的地址 505 | if (newtable == oldtable) {                 // 如果相同 506 | if (so->fill == so->used) {              // 如果使用的相同则什么都不做 507 | /* No dummies, so no point doing anything. */ 508 | return 0; 509 | } 510 | /* We're not going to resize it, but rebuild the 511 | table anyway to purge old dummy entries. 512 | Subtle: This is *necessary* if fill==size, 513 | as set_lookkey needs at least one virgin slot to 514 | terminate failing searches. If fill < size, it's 515 | merely desirable, as dummies slow searches. */ 516 | assert(so->fill > so->used); 517 | memcpy(small_copy, oldtable, sizeof(small_copy)); // 将数据拷贝到set_lookkey中 518 | oldtable = small_copy;                   519 | } 520 | } 521 | else { 522 | newtable = PyMem_NEW(setentry, newsize); // 新申请内存 523 | if (newtable == NULL) {                     // 如果为空则申请内存失败报错 524 | PyErr_NoMemory(); 525 | return -1; 526 | } 527 | } 528 | 529 | /* Make the set empty, using the new table. */ 530 | assert(newtable != oldtable); // 检查新申请的与就table不同 531 | memset(newtable, 0, sizeof(setentry) * newsize);        // 新申请的内存置空 532 | so->mask = newsize - 1; // 设置新的size 533 | so->table = newtable; // 重置table指向新table 534 | 535 | /* Copy the data over; this is refcount-neutral for active entries; 536 | dummy entries aren't copied over, of course */ 537 | newmask = (size_t)so->mask; // 获取新的mask 538 | if (so->fill == so->used) { // 如果使用的与曾经使用的数量相同 539 | for (entry = oldtable; entry <= oldtable + oldmask; entry++) { 540 | if (entry->key != NULL) { 541 | set_insert_clean(newtable, newmask, entry->key, entry->hash);  // 如果值不为空则插入到新的table中 542 | } 543 | } 544 | } else { 545 | so->fill = so->used;                        // 如果不相同则重置fill为used的值 546 | for (entry = oldtable; entry <= oldtable + oldmask; entry++) { 547 | if (entry->key != NULL && entry->key != dummy) {     // 检查如果不为dummy并且key不为空的情况下 548 | set_insert_clean(newtable, newmask, entry->key, entry->hash);  // 重新插入该列表该值 549 | } 550 | } 551 | } 552 | 553 | if (is_oldtable_malloced)                       // 如果两个表相同则删除旧table 554 | PyMem_DEL(oldtable); 555 | return 0; // 返回0 556 | } 557 | 558 | ``` 559 | 560 | 主要是检查是否 table 相同并且需要重新 resize 的值,然后判断是否 fill 与 used 相同,如果相同则全部插入,如果不同,则遍历旧 table 讲不为空并且不为 dummy 的值插入到新表中; 561 | 562 | `源文件:`[Objects/setobject.c](https://github.com/python/cpython/blob/1bf9cc509326bc42cd8cb1650eb9bf64550d817e/Objects/setobject.c#L267) 563 | 564 | ```c 565 | static void 566 | set_insert_clean(setentry *table, size_t mask, PyObject *key, Py_hash_t hash) 567 | { 568 | setentry *entry; 569 | size_t perturb = hash; 570 | size_t i = (size_t)hash & mask;         // 计算索引 571 | size_t j; 572 | 573 | while (1) { 574 | entry = &table[i]; // 获取当前entry 575 | if (entry->key == NULL) // 如果为空则跳转值found_null设置key与hash 576 | goto found_null; 577 | if (i + LINEAR_PROBES <= mask) { // 如果没有找到空值则通过该索引偏移9位去查找空余位置 578 | for (j = 0; j < LINEAR_PROBES; j++) { 579 | entry++; 580 | if (entry->key == NULL) // 如果为空则跳转到found_null 581 | goto found_null; 582 | } 583 | } 584 | perturb >>= PERTURB_SHIFT; // 计算下一个索引值继续寻找 585 | i = (i * 5 + 1 + perturb) & mask; 586 | } 587 | found_null: 588 | entry->key = key; 589 | entry->hash = hash; 590 | } 591 | ``` 592 | 593 | set 的 resize 的操作基本如上所述。 594 | -------------------------------------------------------------------------------- /objects/set-object/set-insert-nine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/set-object/set-insert-nine.png -------------------------------------------------------------------------------- /objects/set-object/set-insert-one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/set-object/set-insert-one.png -------------------------------------------------------------------------------- /objects/set-object/set-insert-two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/set-object/set-insert-two.png -------------------------------------------------------------------------------- /objects/set-object/set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/objects/set-object/set.png -------------------------------------------------------------------------------- /objects/simple-interpreter/index.md: -------------------------------------------------------------------------------- 1 | # Python 字符串 对象 2 | -------------------------------------------------------------------------------- /objects/str-object/index.md: -------------------------------------------------------------------------------- 1 | # 实现简版 Python 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "serve": "gitbook serve", 4 | "build": "gitbook build" 5 | }, 6 | "dependencies": { 7 | "gitbook": "^2.6.9", 8 | "gitbook-cli": "^2.3.2", 9 | "gitbook-plugin-back-to-top-button": "^0.1.4", 10 | "gitbook-plugin-edit-link": "^2.0.2", 11 | "gitbook-plugin-github": "^2.0.0", 12 | "gitbook-plugin-github-buttons": "^2.1.0", 13 | "gitbook-plugin-page-toc-button": "^0.1.1", 14 | "gitbook-plugin-prism": "^2.4.0", 15 | "gitbook-plugin-search-plus": "0.0.11", 16 | "gitbook-plugin-sharing-plus": "0.0.2", 17 | "gitbook-plugin-splitter": "0.0.8", 18 | "gitbook-plugin-tbfed-pagefooter": "0.0.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /preface/code-organization/index.md: -------------------------------------------------------------------------------- 1 | # Python 源代码的组织 2 | 3 | ## 源代码下载 4 | 5 | ### 方式 1:GitHub 6 | 7 | Python 源代码可以在 GitHub 上方便的获取,执行: 8 | 9 | ```console 10 | git clone https://github.com/python/cpython.git 11 | git checkout v3.7.0 12 | ``` 13 | 14 | 即可获取 `Python 3.7.0` 版本的代码。 15 | 16 | ### 方式 2:Python 官方网站 17 | 18 | 访问 https://www.python.org/downloads/release/python-370/ ,下拉至页面最下方,可选择下载 tarball 源码包。 19 | 亦可执行: 20 | 21 | ```console 22 | wget https://www.python.org/ftp/python/3.7.0/Python-3.7.0.tar.xz 23 | ``` 24 | 25 | 即可获取 `Python 3.7.0` 版本的代码。 26 | 27 | ## 目录结构 28 | 29 | 进入源码目录,我们可以看到该目录下主要 包含以下文件(夹): 30 | 31 | ```console 32 | . 33 | ├── Doc 34 | ├── Grammar 35 | ├── Include 36 | ├── LICENSE 37 | ├── Lib 38 | ├── Mac 39 | ├── Makefile.pre.in 40 | ├── Misc 41 | ├── Modules 42 | ├── Objects 43 | ├── PC 44 | ├── PCbuild 45 | ├── Parser 46 | ├── Programs 47 | ├── Python 48 | ├── README.rst 49 | ├── Tools 50 | ├── aclocal.m4 51 | ├── config.guess 52 | ├── config.sub 53 | ├── configure 54 | ├── configure.ac 55 | ├── install-sh 56 | ├── m4 57 | ├── pyconfig.h.in 58 | └── setup.py 59 | ``` 60 | 61 | 其中: 62 | 63 | **Include 目录**:包含了 Python 提供的所有头文件,如果用户需要自己用 C 或 C++来编写自定义模块扩展 Python,那么就需要用到这里提供的头文件。 64 | 65 | **Lib 目录**:包含了 Python 自带的所有标准库,且都是用 Python 语言编写的。 66 | 67 | **Modules 目录**:包含了所有用 C 语言编写的模块,比如 math、hashlib 等。它们都是那些对速度要求非常严格的模块。而相比而言,Lib 目录下则是存放一些对速度没有太严格要求的模块,比如 os。 68 | 69 | **Parser 目录**:包含了 Python 解释器中的 Scanner 和 Parser 部分,即对 Python 源代码进行词法分析和语法分析的部分。除此以外,此目录还包含了一些有用的工具,这些工具能够根据 Python 语言的语法自动生成 Python 语言的词法和语法分析器,与 YACC 非常类似。 70 | 71 | **Objects 目录**:包含了所有 Python 的内建对象,包括整数、list、dict 等。同时,该目录还包括了 Python 在运行时需要的所有的内部使用对象的实现。 72 | 73 | **Python 目录**:包含了 Python 解释器中的 Compiler 和执行引擎部分,是 Python 运行的核心所在。 74 | 75 | **PCbuild 目录**:包含了 Visual Studio 2003 的工程文件,研究 Python 源代码就从这里开始(本书将采用 Visual Studio 2017 对 Python 进行编译)。 76 | 77 | **Programs 目录**:包含了 Python 二进制可执行文件的源码。 78 | -------------------------------------------------------------------------------- /preface/modify-code/index.md: -------------------------------------------------------------------------------- 1 | # 修改 Python 源码 2 | 3 | ## 在源代码中 Print 4 | 5 | 在接下来研究源码的过程中,我们可能会对某些语句的逻辑感到好奇,需要输出中间结果。 6 | 这就需要借助 Python C API 中打印对象的接口: 7 | 8 | `源文件:`[Objects/object.c](https://github.com/python/cpython/blob/v3.7.0/Objects/object.c#L339) 9 | 10 | ```c 11 | int 12 | PyObject_Print(PyObject *op, FILE *fp, int flags) 13 | ``` 14 | 15 | 比如,我们希望在解释器交互界面中打印整数值的时候输出一段字符串,则我们可以修改如下函数: 16 | 17 | `源文件:`[Objects/longobject.c](https://github.com/python/cpython/blob/v3.7.0/Objects/longobject.c#L1762) 18 | 19 | ```c 20 | static PyObject * 21 | long_to_decimal_string(PyObject *aa) 22 | { 23 | PyObject *str = PyUnicode_FromString("I am always before int"); 24 | PyObject_Print(str, stdout, 0); 25 | printf("\n"); 26 | 27 | PyObject *v; 28 | if (long_to_decimal_string_internal(aa, &v, NULL, NULL, NULL) == -1) 29 | return NULL; 30 | return v; 31 | } 32 | ``` 33 | 34 | 函数实现中的前 3 行为我们加入的代码,其中: 35 | 36 | - `PyUnicode_FromString` 用于把 C 中的原生字符数组转换为出 Python 中的字符串(Unicode)对象 37 | - `PyObject_Print` 则将转换好的字符串对象打印至我们指定的标准输出(`stdout`) 38 | 39 | 对 Python 重新进行编译,在 Unix 上可执行: 40 | 41 | ```console 42 | make && make bininstall 43 | ``` 44 | 45 | 运行编译后的 Python,输入 print 语句即可看到我们希望的结果: 46 | 47 | ```python 48 | >>> print(1) 49 | 'I am always before int' 50 | 1 51 | ``` 52 | -------------------------------------------------------------------------------- /preface/unix-linux-build/index.md: -------------------------------------------------------------------------------- 1 | # UNIX/Linux 环境下编译 Python 2 | 3 | 在 UNIX/Linux 环境下编译 Python 较为简单,主要分为两个步骤: 4 | 5 | 1. 环境准备(准备 Python 所依赖的必要环境) 6 | 2. 编译、安装 7 | 8 | ## 环境准备 9 | 10 | ### 常规操作系统中 11 | 12 | 编译 Python 前通常需要在系统上安装以下库: 13 | 14 | - `gcc` // 编译工具 15 | - `zlib` // 压缩、解压相关库 16 | - `libffi` // Python 所以来的用于支持 C 扩展的库 17 | - `openssl` // 安全套接字层密码库,Linux 中通常已具备 18 | 19 | 不同的发行版,安装方式和包名称也不尽相同。 20 | 21 | 对于 `Debian/Ubuntu`,执行: 22 | 23 | ```console 24 | sudo apt install -y zlib1g zlib1g-dev libffi-dev openssl libssl-dev 25 | ``` 26 | 27 | 对于 `RedHat/CentOS/Fedora`,执行: 28 | 29 | ```console 30 | yum install -y zlib zlib-devel libffi-devel openssl openssl-devel 31 | ``` 32 | 33 | 对于 `macOS`,执行: 34 | 35 | ```console 36 | xcode-select --install 37 | ``` 38 | 39 | ### 运行于 Docker 的操作系统中 40 | 41 | Docker 版的 Linux 发行版可能会有较多的库未安装,除了安装上一小节提及的库外,其他缺失库可根据情况自行安装: 42 | 43 | - `bzip2` // 压缩库 44 | - `readline` // GNU Readline 是一个软件库,它为使用命令行界面(如 Bash)的交互式程序提供了行编辑和历史功能 45 | - `sqlite` // 由 C 编写的小型数据库 46 | - `libuuid` // 跨平台的开源的 uuid 操作库 47 | - `gdbm` // 小型的数据库系统 48 | - `xz` // 压缩解压工具 49 | - `tk-devel` // 图形用户界面开发工具 50 | 51 | 对于 `Debian/Ubuntu`,执行: 52 | 53 | ```console 54 | sudo apt-get install bzip2 libbz2-dev sqlite3 libsqlite3-dev libreadline6 libreadline6-dev libgdbm-dev uuid-dev tk-dev 55 | ``` 56 | 57 | 对于 `RedHat/CentOS/Fedora`,执行: 58 | 59 | ```console 60 | yum install bzip2 bzip2-devel readline-devel sqlite-devel libuuid-devel gdbm-devel xz-devel tk-devel 61 | ``` 62 | 63 | ## 编译、安装 64 | 65 | 进入 Python 源码根目录,执行以下命令: 66 | 67 | ```console 68 | ./configure 69 | make 70 | make install 71 | ``` 72 | 73 | Python 将会被编译,并安装在默认目录中。若您希望将 Python 安装在特定目录,则需要在一开始修改 `configure` 命令为: 74 | 75 | ```console 76 | ./configure --prefix= 77 | ``` 78 | 79 | 在指定目录中: 80 | 81 | - **bin 目录** 存放的是可执行文件 82 | - **include 目录** 存放的是 Python 源码的头文件 83 | - **lib 目录** 存放的是 Python 标准库 84 | - **lib/python3.7/config-3.7m-{platform} 目录** 存放的是 libpython3.7m.a,该静态库用于使用 C 语言进行扩展。`{platform}` 代表平台,比如在 Mac OS 上为 “darwin”,在 Linux 上为 “x86_64-linux-gnu” 85 | - **share 目录** 存放的是帮助等文件 86 | 87 | 默认情况下,编译的 Python 是静态链接(libpython3.7m.a)。如果希望编译的 Python 是动态链接(libpython3.7m.so),则需要在一开始修改`configure` 命令为: 88 | 89 | ```console 90 | ./configure --enable-shared 91 | ``` 92 | 93 | 如需重新编译,请首先执行: 94 | 95 | ```console 96 | make clean 97 | ``` 98 | 99 | 再执行本节开头处的命令即可。 100 | -------------------------------------------------------------------------------- /preface/windows-build/build-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/preface/windows-build/build-files.png -------------------------------------------------------------------------------- /preface/windows-build/index.md: -------------------------------------------------------------------------------- 1 | # Windows 环境下编译 Python 2 | 3 | 在 Windows 环境下编译 Python 可完全通过界面操作,主要分为两个步骤: 4 | 5 | 1. 环境准备 6 | 2. 编译 7 | 8 | ## 环境准备 9 | 10 | 在 Python 3.6 及之后的版本可以使用微软的 11 | [Visual Studio 2017](https://visualstudio.microsoft.com/zh-hans/vs/) 进行编译,选择社区版就足够了。 12 | 13 | 在下载完成后,需要注意安装环节的选项。由于 Python 3.7 所使用的 Windows SDK 的版本为 `10.0.17134.0`, 14 | 所以需要选择安装该 SDK,如下图所示: 15 | ![](vs2017-installation.png) 16 | 17 | ## 编译 18 | 19 | 进入 Python 源码根目录,打开 `PCbuild\pcbiuld.sln` 解决方案,而后进行一些设置: 20 | 21 | 在左侧的解决方案目录的顶端,右键选择“属性”,以打开属性界面(如下图所示)。 22 | 23 | 24 | 25 | 由于我们只是研究 Python 的核心部分,可以选择不编译标准库和外部依赖,在“配置属性”->“配置”中仅勾选 26 | python 和 pythoncore,然后点击“确定”(如下图所示)。 27 | 28 | 此外,默认情况下的编译设置是 Debug、32 位,您也可以根据自己的需求调整成 Release 或 64 位。 29 | 30 | 31 | 32 | 在左侧的解决方案目录中选择 python,右键选择“生成”,以进行编译: 33 | 34 | 35 | 36 | 编译结束后,生成的文件存放在`PCbuild\win32`目录下(如下图所示),打开`python_d`即可打开新生成的 Python 3.7 解释器。 37 | 38 | 39 | 40 | 41 | ## 更多内容 42 | 更多关于在 Windows 上进行编译和开发 Python 的内容见[官方指南](https://devguide.python.org/setup/#windows-compiling) 43 | -------------------------------------------------------------------------------- /preface/windows-build/vs2017-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/preface/windows-build/vs2017-build.png -------------------------------------------------------------------------------- /preface/windows-build/vs2017-configure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/preface/windows-build/vs2017-configure.png -------------------------------------------------------------------------------- /preface/windows-build/vs2017-installation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/preface/windows-build/vs2017-installation.png -------------------------------------------------------------------------------- /preface/windows-build/vs2017-properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flaggo/python3-source-code-analysis/adf1ad6293bb8aefb0f4bc66bc99836c97032301/preface/windows-build/vs2017-properties.png --------------------------------------------------------------------------------