forked from zpoint/CPython-Internals
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathenum_cn.md
More file actions
183 lines (127 loc) · 5.9 KB
/
Copy pathenum_cn.md
File metadata and controls
183 lines (127 loc) · 5.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# enum
# 目录
* [相关位置文件](#相关位置文件)
* [内存构造](#内存构造)
* [示例](#示例)
* [normal](#normal)
* [en_longindex](#en_longindex)
# 相关位置文件
* cpython/Objects/enumobject.c
* cpython/Include/enumobject.h
* cpython/Objects/clinic/enumobject.c.h
# 内存构造
**enumerate** 是一个类型, **enumerate** 的实例是一个可迭代对象, 你可以在迭代的过程中同时获得这个迭代的对象和一个计数器

# 示例
## normal
```python3
def gen():
yield "I"
yield "am"
yield "handsome"
e = enumerate(gen())
>>> type(e)
<class 'enumerate'>
```
在迭代对象 **e** 之前, 它的 **en_index** 字段为 0, **en_sit** 指向了真正的 **generator** 对象, **en_result** 指向了一个有两个空值的 **tuple**
我们后面会提到 **en_longindex** 的作用

```python3
>>> t1 = next(e)
>>> t1
(0, 'I')
>>> id(t1)
4469348888
```
现在 **en_index** 字段变成了 1, **en_result** 里面指向的元组对象为最近一次返回的元组对象, 元组里面的两个元素都改变了, 但是 **en_result** 里的地址没有变化, 没有变化的原因不是 [tuple 缓冲池](https://github.com/zpoint/CPython-Internals/blob/master/BasicObject/tuple/tuple_cn.md#free-list) 机制的原因
而是和 **enumerate** 的迭代函数使用的技巧有关
```c
static PyObject *
enum_next(enumobject *en)
{
/* omit */
PyObject *result = en->en_result;
/* omit */
if (result->ob_refcnt == 1) {
/*
tuple 对象的引用计数器为 is 1
说明当前唯一引用这个 tuple 对象的就是这个 enumerate 实例本身
既然这个 tuple 对象的两个旧元素已经不需要了
我们可以把这两个元素设置为新的元素, 并返回这个 tuple 对象
*/
Py_INCREF(result);
old_index = PyTuple_GET_ITEM(result, 0);
old_item = PyTuple_GET_ITEM(result, 1);
PyTuple_SET_ITEM(result, 0, next_index);
PyTuple_SET_ITEM(result, 1, next_item);
Py_DECREF(old_index);
Py_DECREF(old_item);
return result;
}
/*
到达这里, 这个 tuple 对象的引用计数器不为 1, 处理自己本身还有其他的变量在使用它
我们不能重置这个 tuple 里面的元素
只能创建新的返回给调用者
*/
result = PyTuple_New(2);
if (result == NULL) {
Py_DECREF(next_index);
Py_DECREF(next_item);
return NULL;
}
PyTuple_SET_ITEM(result, 0, next_index);
PyTuple_SET_ITEM(result, 1, next_item);
return result;
}
```
很明显了, 因为旧的 **tuple** `tuple object -> (None, None)` 对象唯一的引用来自当前的 **enumerate** 对象, **enum_next** 会把这个 **tuple** 对象的第 0 个元素变为 0, 第 1 个元素变为 'I', 之后把这个旧 tuple 返回
所以 **en_result** 指向的地址未发生改变, 并且 **en_result** 指向的对象为这次迭代返回的对象

这个 **tuple** 对象 `(0, 'I') # id(4469348888)` 现在的引用计数器变为 2 了, 一个来自 **enumerate** 实例的引用, 还有一个来自变量名称 t1, **enum_next** 这次会进入下面的分支, 创建一个新的 **tuple** 对象并返回而不是重置那个旧的 **tuple** 对象
**en_index** 中的数值增加了, **en_result** 仍然指向这个旧的 **tuple** 对象`(0, 'I') # id(4469348888)`
```python3
>>> next(e)
(1, 'am')
```

`del t1` 语句执行之后, 这个 tuple 对象 `(0, 'I') # id(4469348888)` 的引用计数器变回了 1, 此时 **enum_next** 会像上面一样重置这个 **tuple** 对象, **en_result** 仍然指向这个对象, 并且这次返回的为 **en_result** 指向的对象
```python3
>>> del t1 # decrement the reference count of the object referenced by t1
>>> next(e)
(2, 'handsome')
```

结束标记是被 **en_sit** 里面存储的对象所存储的, **enumerate** 本身不存储结束标记等信息
```python3
>>> next(e)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
```

## en_longindex
通常情况下, 计数器的值是存储在 **en_index** 里面的, **en_index** 的类型是 **Py_ssize_t**, 我们来看下 **Py_ssize_t** 的定义
```c
#ifdef HAVE_SSIZE_T
typedef ssize_t Py_ssize_t;
#elif SIZEOF_VOID_P == SIZEOF_SIZE_T
typedef Py_intptr_t Py_ssize_t;
#else
# error "Python needs a typedef for Py_ssize_t in pyport.h."
#endif
```
大部分情况下他是一个 **ssize_t**, 32位操作系统下为 int, 64位下为 **long int**
在我的机器上他是 **long int**
如果这个计数器的值大到这 64个 bit 都装不下呢?
```python3
e = enumerate(gen(), 1 << 62)
```
这个时候 **en_index** 是可以放得下这个值的

**en_index** 能表示的最大的值为 ((1 << 63) - 1) (PY_SSIZE_T_MAX)
现在实际上的计数器的值已经比 PY_SSIZE_T_MAX 还要大了, 此时 **en_longindex** 会被用来存储真正的计数器
**en_longindex** 指向的是一个 [PyLongObject(python 类型 int)](https://github.com/zpoint/CPython-Internals/blob/master/BasicObject/long/long_cn.md), 对象, 可以表示任意长度的整数大小
```python3
>>> e = enumerate(gen(), (1 << 63) + 100)
```
