forked from zpoint/CPython-Internals
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgen_cn.md
More file actions
584 lines (425 loc) · 17.7 KB
/
Copy pathgen_cn.md
File metadata and controls
584 lines (425 loc) · 17.7 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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
# gen
# 目录
* [相关位置文件](#相关位置文件)
* [generator](#generator)
* [内存构造](#内存构造-generator)
* [示例 generator](#示例-generator)
* [coroutine](#coroutine)
* [内存构造](#内存构造-coroutine)
* [示例 coroutine](#示例-coroutine)
* [async generator](#async-generator)
* [内存构造](#内存构造-async-generator)
* [示例 async generator](#示例-async-generator)
* [free list](#free-list)
# 相关位置文件
* cpython/Objects/genobject.c
* cpython/Include/genobject.h
# generator
## 内存构造 generator
**generator**, **coroutine** 和 **async generator** 共享一大部分的定义
```c
#define _PyGenObject_HEAD(prefix) \
PyObject_HEAD \
/* Note: gi_frame can be NULL if the generator is "finished" */ \
struct _frame *prefix##_frame; \
/* True if generator is being executed. */ \
char prefix##_running; \
/* The code object backing the generator */ \
PyObject *prefix##_code; \
/* List of weak reference. */ \
PyObject *prefix##_weakreflist; \
/* Name of the generator. */ \
PyObject *prefix##_name; \
/* Qualified name of the generator. */ \
PyObject *prefix##_qualname; \
_PyErr_StackItem prefix##_exc_state;
```
**generator** 对象实际上的定义仅不到4行代码
```c
typedef struct {
/* The gi_ prefix is intended to remind of generator-iterator. */
_PyGenObject_HEAD(gi)
} PyGenObject;
```
我们可以把他扩展一下
```c
typedef struct {
struct _frame *gi_frame;
char gi_running;
PyObject *gi_code;
PyObject *gi_weakreflist;
PyObject *gi_name;
PyObject *gi_qualname;
_PyErr_StackItem gi_exc_state;
} PyGenObject;
```
根据扩展后的代码可以直观的画出内存构造

## 示例 generator
我们来定义一个 **generator** 并一步一步的迭代他
```python3
def fib(n):
t = 0
i = 1
j = 1
r = 0
result = None
while t <= n:
print("result", repr(result))
if t < 2:
result = yield i
else:
r = i + j
result = yield r
i = j
j = r
t += 1
try:
1 / 0
except ZeroDivisionError:
r = yield "ZeroDivisionError"
print(repr(r))
try:
import empty
except ModuleNotFoundError:
result = yield "ModuleNotFoundError"
print("result", repr(result))
finally:
result = yield "ModuleNotFoundError finally"
print("result", repr(result))
raise StopIteration
>>> f = fib(5)
>>> type(f)
<class 'generator'>
>>> type(fib)
<class 'function'>
>>> f.gi_frame.f_lasti
-1
```
我们初始化了一个新的 **generator**, **gi_frame** 字段的对象里储存的 **f_lasti** 和操作系统概念里的 program counter 有点类似, 你可以把他理解成 python 虚拟机中的 program counter, 他指向当前 **gi_code** 对象里包含的可执行代码块的位置
```python3
>>> fib.__code__
<code object fib at 0x1041069c0, file "<stdin>", line 1>
>>> f.gi_code
<code object fib at 0x1041069c0, file "<stdin>", line 1>
```
对象 f 里面的 **gi_code** 正是一个 **code** 对象, 这个对象包含了函数 **fib** 所需的信息
**gi_running** 为 0, 表示 **generator** 当前没有在运行中
**gi_name** 和 **gi_qualname** 都指向同一个 **unicode** 对象, **gi_exc_state** 里面的各个字段的值都为 0x00

```python3
>>> r = f.send(None)
result None
>>> f.gi_frame.f_lasti
52
>>> repr(r)
'1'
```
对象**f**每个字段的值都没有发生变化
但是 **gi_frame** 中存储的 **f_lasti** 现在指向了 52(第一个 **yield** 出现的位置)

迭代多一次, 由于 while 循环的原因, **f_lasti** 仍然指向同个位置
```python3
>>> r = f.send("handsome")
result 'handsome'
>>> f.gi_frame.f_lasti
52
>>> repr(r)
'1'
```
再次调用 send, **f_lasti** 此时指向第二个 **yield** 出现的位置
```python3
>>> r = f.send("handsome2")
result 'handsome2'
>>> f.gi_frame.f_lasti
68
>>> repr(r)
'2'
```
重复迭代
```python3
>>> r = f.send("handsome3")
result 'handsome3'
>>> f.gi_frame.f_lasti
68
>>> repr(r)
'3'
>>> r = f.send("handsome4")
result 'handsome4'
>>> f.gi_frame.f_lasti
68
>>> repr(r)
'5'
>>> r = f.send("handsome5")
result 'handsome5'
>>> f.gi_frame.f_lasti
68
>>> repr(r)
'8'
```
现在, 通过 break 跳出了 while 循环
**f_lasti** 指向的位置是 第一个 **except** 发生的位置, **exc_type** 指向这个异常的类型, **exc_value** 指向这个异常的实例, **exc_traceback** 指向异常追踪 traceback 对象
```python3
>>> r = f.send("handsome6")
>>> f.gi_frame.f_lasti
120
>>> repr(r)
"'ZeroDivisionError'"
```

**f_lasti** 在第二个 **except** 的位置上, **exc_type**, **exc_value**, 和 **exc_traceback** 都和异常 ModuleNotFoundError 相关联
```python3
>>> r = f.send("handsome7")
'handsome7'
>>> f.gi_frame.f_lasti
168
>>> repr(r)
"'ModuleNotFoundError'"
```

**f_lasti** 在第一个 **finally** 的位置上, 异常 ModuleNotFoundError 已经处理完成, 异常堆的堆顶现在是一个 **ZeroDivisionError**
实际上异常处理相关的信息是记录在 [frame](https://github.com/zpoint/CPython-Internals/blob/master/Interpreter/frame/frame_cn.md) 对象中的, 这里的 **gi_exec_state** 只是用来表示当前迭代器是否有异常发生以及最近一个异常是什么. 有关异常处理机制请参考 [exception](https://github.com/zpoint/CPython-Internals/blob/master/Interpreter/exception/exception_cn.md)
```python3
>>> r = f.send("handsome8")
result 'handsome8'
>>> f.gi_frame.f_lasti
198
>>> repr(r)
"'ModuleNotFoundError finally'"
```

现在 **StopIteration** 被抛出
**gi_frame** 中的 frameObject 被释放了, 变成了一个空指针, 表明这个 generator 已经结束了
并且 **gi_exc_state** 中的各个字段也重置了
```python3
>>> r = f.send("handsome9")
result 'handsome9'
Traceback (most recent call last):
File "<stdin>", line 30, in fib
StopIteration
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: generator raised StopIteration
>>> f.gi_frame.f_lasti
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'f_lasti'
```

# coroutine
## 内存构造 coroutine
**coroutine** 类型和 **generator** 类型的大部分定义是相同的
**coroutine** 独有的一个字段叫做 **cr_origin**, 用来追踪当前的调用栈用的(这里面的数据都是从 **cr_frame** 对象中查找获得)
默认情况下 **cr_origin** 是不启动的, 需要通过 **sys.set_coroutine_origin_tracking_depth** 去启动这个功能, 可以查看文档获得更多细节 [docs.python.org(set_coroutine_origin_tracking_depth)](https://docs.python.org/3/library/sys.html#sys.set_coroutine_origin_tracking_depth)

## 示例 coroutine
我们可以来跑一个 **coroutine** 类型的示例, 理解一下各个字段的意义
我向往常一样更改了部分源代码, 所以我的 **repr** 会打印出更多的信息
```python3
import sys
import time
import asyncio
sys.set_coroutine_origin_tracking_depth(100)
cor_list = list()
async def cor(recursive_depth=1):
t1 = time.time()
try:
await asyncio.sleep(3)
1 / 0
except ZeroDivisionError:
if recursive_depth > 0:
r = cor(recursive_depth-1)
cor_list.append(r)
await r
t2 = time.time()
print("recursive_depth: %d, cost %.2f seconds" % (recursive_depth, t2 - t1))
def pr_cor_list():
for index, each in enumerate(cor_list):
print("index: %d, id: %d, each.cr_frame.f_lasti: %s" % (index, id(each), "None object" if each.cr_frame is None else str(each.cr_frame.f_lasti)))
print(repr(each))
async def test():
c = cor()
cor_list.append(c)
ts = time.time()
pending = [c]
pr_cor_list()
while pending:
done, pending = await asyncio.wait(pending, timeout=2)
ts_now = time.time()
print("%.2f seconds elapse" % (ts_now - ts, ))
pr_cor_list()
if __name__ == "__main__":
asyncio.run(test())
```
你调用一个通过 **async** 定义的函数的时候, 产生的是一个类型为 **coroutine** 的对象
```python3
>>> c = cor()
>>> type(c)
<class 'coroutine'>
```
在 **test** 函数中, 第一个 **await** 声明之前的瞬间
这是我电脑中字段 **cr_origin** 中的内容, 是自下而上的调用栈信息
```python3
>>> cor_list[0].cr_origin
(('<stdin>', 2, 'test'), ('/Users/zpoint/Desktop/cpython/Lib/asyncio/events.py', 81, '_run'), ('/Users/zpoint/Desktop/cpython/Lib/asyncio/base_events.py', 1765, '_run_once'), ('/Users/zpoint/Desktop/cpython/Lib/asyncio/base_events.py', 544, 'run_forever'), ('/Users/zpoint/Desktop/cpython/Lib/asyncio/base_events.py', 576, 'run_until_complete'), ('/Users/zpoint/Desktop/cpython/Lib/asyncio/runners.py', 43, 'run'), ('<stdin>', 2, '<module>'))
```

在第 2.01 秒时, 各个字段中的内容并未发生改变, 此时 **coroutine.cr_frame** 中的 **f_lasti** 指向了 **cor** 函数中第一个 **await** 的位置

在第 4.01 秒时, cor_list[0] 中的 **f_lasti** 指向了 **await r** 这个位置, 值为 86
**exc_type**, **exc_value** and **exc_traceback** 保存了 **ZeroDivisionError** 的信息, 和 **generator** 对象的处理方式相同
cor_list[1] 现在停在了 **await asyncio.sleep(3)** 这个位置上, **f_lasti** 中的值为 20
cor_list[1] 的 **cr_code** 和 cor_list[0] 的 **cr_code** 相同, 但是 **cr_frame** 却不同
每一个函数调用都会产生一个新的 frame 对象与之关联, frame 对象可以参考这篇 [frame object](https://github.com/zpoint/CPython-Internals/blob/master/Interpreter/frame/frame_cn.md)

```python3
>>> cor_list[1].cr_origin
(('<stdin>', 8, 'cor'), ('/Users/zpoint/Desktop/cpython/Lib/asyncio/events.py', 81, '_run'), ('/Users/zpoint/Desktop/cpython/Lib/asyncio/base_events.py', 1765, '_run_once'), ('/Users/zpoint/Desktop/cpython/Lib/asyncio/base_events.py', 544, 'run_forever'), ('/Users/zpoint/Desktop/cpython/Lib/asyncio/base_events.py', 576, 'run_until_complete'), ('/Users/zpoint/Desktop/cpython/Lib/asyncio/runners.py', 43, 'run'), ('<stdin>', 2, '<module>'))
```
在 6.01 秒时, **cor_list[0]** 和 **cor_list[1]** 都结束并返回了, 他们的 **cr_frame** 都为空指针, 处理方式和 **generator** 类型类似

# async generator
## 内存构造 async generator
除了 **ag_finalizer**, **ag_hooks_inited** and **ag_closed** 这三个额外添加的字段, **async generator** 的构造和 **generator** 是相同的

## 示例 async generator
**set_asyncgen_hooks** 函数可以设置一个 **firstiter** 和一个 **finalizer**, **firstiter** 会在**async generator** 第一次迭代之前调用, **finalizer** 会在垃圾回收之前进行调用
**asyncio base event loop** 中的 **run_forever** 函数做了如下定义
```python3
def run_forever(self):
...
old_agen_hooks = sys.get_asyncgen_hooks()
sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook,
finalizer=self._asyncgen_finalizer_hook)
try:
...
finally:
sys.set_asyncgen_hooks(*old_agen_hooks)
```
你也可以定义你自己的 **firstiter** 和 **finalizer**, 更多详细信息参考 [python3-doc set_asyncgen_hooks](https://docs.python.org/3/library/sys.html#sys.set_asyncgen_hooks)
```python3
# example of set_asyncgen_hooks
import sys
async def async_fib(n):
yield 1
def firstiter(async_gen):
print("in firstiter: ", async_gen)
def finalizer(async_gen):
print("in finalizer: ", async_gen)
sys.set_asyncgen_hooks(firstiter, finalizer)
>>> f = async_fib(3)
>>> f.__anext__()
in firstiter: <async_generator object async_fib at 0x10a98f598>
<async_generator_asend at 0x10a7487c8>
```
我们来定义一个 async iterator 并尝试一步步迭代
```python3
import asyncio
async def async_fib(n):
t = 0
i = 1
j = 1
r = 0
result = None
while t <= n:
print("result", repr(result))
await asyncio.sleep(3)
if t < 2:
result = yield i
else:
r = i + j
result = yield r
i = j
j = r
t += 1
class AsendTest(object):
def __init__(self, n):
self.f = async_fib(n)
self.loop = asyncio.get_event_loop()
async def make_the_call(self, val):
r = await self.f.asend(val)
print("repr asend", repr(r))
def __call__(self, *args, **kwargs):
self.loop.run_until_complete(self.make_the_call(args[0]))
a = AsendTest(3)
>>> type(a.f)
<class 'async_generator'>
```

开始迭代
如果你需要 `__aiter__`, `__anext__` 等函数的相关信息, 可以参考 [pep-0525](https://www.python.org/dev/peps/pep-0525/)
```python3
>>> a(None)
result None
repr asend 1
>>> a.f.ag_frame.f_lasti
68
```
**ag_weakreflist** 指向了一个 **BaseEventLoop(`asyncio->base_events.py`)** 创建的弱引用, loop 需要保留与之相关的所有 **async generator** 的信息, 这样在出现异常/退出的时候可以把这些活动中的 **async generator** 通通关掉, 可以读这部分代码看看 [source code](https://github.com/python/cpython/blob/3.7/Lib/asyncio/base_events.py)
**ag_finalizer** 现在指向了一个 **finalizer**, 这个 **finalizer** 是被 `BaseEventLoop` 通过 **sys.set_asyncgen_hooks** 方法配置的
**ag_hooks_inited** 为 1, 标明当前的 hooks是已配置的状态

第二次 while 循环中, 各个字段中的值未发生改变
```python3
>>> a("handsome")
result 'handsome'
repr asend 1
>>> a.f.ag_frame.f_lasti
68
```
现在 **f_lasti** 指向了函数 **async_fib** 的第二个 **yield** 的位置
```python3
>>> a("handsome2")
result 'handsome2'
repr asend 2
>>> a.f.ag_frame.f_lasti
84
```

```python3
>>> a("handsome3")
result 'handsome3'
repr asend 3
>>> a.f.ag_frame.f_lasti
84
>>> a("handsome4")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 9, in __call__
File "/Users/zpoint/Desktop/cpython/Lib/asyncio/base_events.py", line 589, in run_until_complete
return future.result()
File "<stdin>", line 6, in make_the_call
StopAsyncIteration
```
现在 **ag_closed** 被设置为 1(只有在 async generator 抛出了 **StopAsyncIteration** 异常, 或者 关联的 `aclose()` 方法被调用的情况下会被设置为 1)
并且 **ag_frame** 被释放了

## free list
在类型 **async_generator_asend** 和 **async_generator_wrapped_value** 上面使用了free list(缓冲池)机制
```c
#ifndef _PyAsyncGen_MAXFREELIST
#define _PyAsyncGen_MAXFREELIST 80
#endif
static _PyAsyncGenWrappedValue *ag_value_freelist[_PyAsyncGen_MAXFREELIST];
static int ag_value_freelist_free = 0;
static PyAsyncGenASend *ag_asend_freelist[_PyAsyncGen_MAXFREELIST];
static int ag_asend_freelist_free = 0;
```
因为这两个类型存活的时间一般都很短, 并且在每一个 **_\_anext_\_** 调用的时候都会实例化他们, 缓冲池机制可以
* 提高 6-10% 的性能
* 减小内存碎片
两个 r 的 id 是相同的, 同个 **async_generator_asend** 对象被重复的进行了使用
```python3
>>> f = async_fib(3)
>>> r = f.asend(None)
>>> type(r)
<class 'async_generator_asend'>
>>> id(r)
4376804088
>>> del r
>>> r = f.asend(None)
>>> id(r)
4376804088
```
