|
| 1 | +# enum |
| 2 | + |
| 3 | +### 目录 |
| 4 | + |
| 5 | +* [相关位置文件](#相关位置文件) |
| 6 | +* [内存构造](#内存构造) |
| 7 | +* [示例](#示例) |
| 8 | + * [normal](#normal) |
| 9 | + * [en_longindex](#en_longindex) |
| 10 | + |
| 11 | +#### 相关位置文件 |
| 12 | + |
| 13 | +* cpython/Objects/enumobject.c |
| 14 | +* cpython/Include/enumobject.h |
| 15 | +* cpython/Objects/clinic/enumobject.c.h |
| 16 | + |
| 17 | +#### 内存构造 |
| 18 | + |
| 19 | +**enumerate** 是一个类型, **enumerate** 的实例是一个可迭代对象, 你可以在迭代的过程中同时获得这个迭代的对象和一个计数器 |
| 20 | + |
| 21 | + |
| 22 | + |
| 23 | +#### 示例 |
| 24 | + |
| 25 | +##### normal |
| 26 | + |
| 27 | + def gen(): |
| 28 | + yield "I" |
| 29 | + yield "am" |
| 30 | + yield "handsome" |
| 31 | + |
| 32 | + e = enumerate(gen()) |
| 33 | + |
| 34 | + >>> type(e) |
| 35 | + <class 'enumerate'> |
| 36 | + |
| 37 | +在迭代对象 **e** 之前, 它的 **en_index** 字段为 0, **en_sit** 指向了真正的 **generator** 对象, **en_result** 指向了一个有两个空值的 **tuple** |
| 38 | + |
| 39 | +我们后面会提到 **en_longindex** 的作用 |
| 40 | + |
| 41 | + |
| 42 | + |
| 43 | + >>> t1 = next(e) |
| 44 | + >>> t1 |
| 45 | + (0, 'I') |
| 46 | + >>> id(t1) |
| 47 | + 4469348888 |
| 48 | + |
| 49 | +现在 **en_index** 字段变成了 1, **en_result** 里面指向的元组对象为最近一次返回的元组对象, 元组里面的两个元素都改变了, 但是 **en_result** 里的地址没有变化, 没有变化的原因不是 [tuple 缓冲池](https://github.com/zpoint/CPython-Internals/blob/master/BasicObject/tuple/tuple_cn.md#free-list) 机制的原因 |
| 50 | + |
| 51 | +而是和 **enumerate** 的迭代函数使用的技巧有关 |
| 52 | + |
| 53 | + static PyObject * |
| 54 | + enum_next(enumobject *en) |
| 55 | + { |
| 56 | + /* omit */ |
| 57 | + PyObject *result = en->en_result; |
| 58 | + /* omit */ |
| 59 | + if (result->ob_refcnt == 1) { |
| 60 | + /* |
| 61 | + tuple 对象的引用计数器为 is 1 |
| 62 | + 说明当前唯一引用这个 tuple 对象的就是这个 enumerate 实例本身 |
| 63 | + 既然这个 tuple 对象的两个旧元素已经不需要了 |
| 64 | + 我们可以把这两个元素设置为新的元素, 并返回这个 tuple 对象 |
| 65 | + */ |
| 66 | + Py_INCREF(result); |
| 67 | + old_index = PyTuple_GET_ITEM(result, 0); |
| 68 | + old_item = PyTuple_GET_ITEM(result, 1); |
| 69 | + PyTuple_SET_ITEM(result, 0, next_index); |
| 70 | + PyTuple_SET_ITEM(result, 1, next_item); |
| 71 | + Py_DECREF(old_index); |
| 72 | + Py_DECREF(old_item); |
| 73 | + return result; |
| 74 | + } |
| 75 | + /* |
| 76 | + 到达这里, 这个 tuple 对象的引用计数器不为 1, 处理自己本身还有其他的变量在使用它 |
| 77 | + 我们不能重置这个 tuple 里面的元素 |
| 78 | + 只能创建新的返回给调用者 |
| 79 | + */ |
| 80 | + result = PyTuple_New(2); |
| 81 | + if (result == NULL) { |
| 82 | + Py_DECREF(next_index); |
| 83 | + Py_DECREF(next_item); |
| 84 | + return NULL; |
| 85 | + } |
| 86 | + PyTuple_SET_ITEM(result, 0, next_index); |
| 87 | + PyTuple_SET_ITEM(result, 1, next_item); |
| 88 | + return result; |
| 89 | + } |
| 90 | + |
| 91 | +很明显了, 因为旧的 **tuple** `tuple object -> (None, None)` 对象唯一的引用来自当前的 **enumerate** 对象, **enum_next** 会把这个 **tuple** 对象的第 0 个元素变为 0, 第 1 个元素变为 'I', 之后把这个旧 tuple 返回 |
| 92 | + |
| 93 | +所以 **en_result** 指向的地址未发生改变, 并且 **en_result** 指向的对象为这次迭代返回的对象 |
| 94 | + |
| 95 | + |
| 96 | + |
| 97 | +这个 **tuple** 对象 `(0, 'I') # id(4469348888)` 现在的引用计数器变为 2 了, 一个来自 **enumerate** 实例的引用, 还有一个来自变量名称 t1, **enum_next** 这次会进入下面的分支, 创建一个新的 **tuple** 对象并返回而不是重置那个旧的 **tuple** 对象 |
| 98 | + |
| 99 | +**en_index** 中的数值增加了, **en_result** 仍然指向这个旧的 **tuple** 对象`(0, 'I') # id(4469348888)` |
| 100 | + |
| 101 | + >>> next(e) |
| 102 | + (1, 'am') |
| 103 | + |
| 104 | + |
| 105 | + |
| 106 | +`del t1` 语句执行之后, 这个 tuple 对象 `(0, 'I') # id(4469348888)` 的引用计数器变回了 1, 此时 **enum_next** 会像上面一样重置这个 **tuple** 对象, **en_result** 仍然指向这个对象, 并且这次返回的为 **en_result** 指向的对象 |
| 107 | + |
| 108 | + >>> del t1 # decrement the reference count of the object referenced by t1 |
| 109 | + >>> next(e) |
| 110 | + (2, 'handsome') |
| 111 | + |
| 112 | + |
| 113 | + |
| 114 | +结束标记是被 **en_sit** 里面存储的对象所存储的, **enumerate** 本身不存储结束标记等信息 |
| 115 | + |
| 116 | + >>> next(e) |
| 117 | + Traceback (most recent call last): |
| 118 | + File "<stdin>", line 1, in <module> |
| 119 | + StopIteration |
| 120 | + |
| 121 | + |
| 122 | + |
| 123 | +##### en_longindex |
| 124 | + |
| 125 | +通常情况下, 计数器的值是存储在 **en_index** 里面的, **en_index** 的类型是 **Py_ssize_t**, 我们来看下 **Py_ssize_t** 的定义 |
| 126 | + |
| 127 | + #ifdef HAVE_SSIZE_T |
| 128 | + typedef ssize_t Py_ssize_t; |
| 129 | + #elif SIZEOF_VOID_P == SIZEOF_SIZE_T |
| 130 | + typedef Py_intptr_t Py_ssize_t; |
| 131 | + #else |
| 132 | + # error "Python needs a typedef for Py_ssize_t in pyport.h." |
| 133 | + #endif |
| 134 | + |
| 135 | +大部分情况下他是一个 **ssize_t**, 32位操作系统下为 int, 64位下为 **long int** |
| 136 | + |
| 137 | +在我的机器上他是 **long int** |
| 138 | + |
| 139 | +如果这个计数器的值大到这 64个 bit 都装不下呢? |
| 140 | + |
| 141 | + e = enumerate(gen(), 1 << 62) |
| 142 | + |
| 143 | +这个时候 **en_index** 是可以放得下这个值的 |
| 144 | + |
| 145 | + |
| 146 | + |
| 147 | +**en_index** 能表示的最大的值为 ((1 << 63) - 1) (PY_SSIZE_T_MAX) |
| 148 | + |
| 149 | +现在实际上的计数器的值已经比 PY_SSIZE_T_MAX 还要大了, 此时 **en_longindex** 会被用来存储真正的计数器 |
| 150 | + |
| 151 | +**en_longindex** 指向的是一个 [PyLongObject(python 类型 int)](https://github.com/zpoint/CPython-Internals/blob/master/BasicObject/long/long_cn.md), 对象, 可以表示任意长度的整数大小 |
| 152 | + |
| 153 | + >>> e = enumerate(gen(), (1 << 63) + 100) |
| 154 | + |
| 155 | + |
| 156 | + |
0 commit comments