Python 生成器原理详解
来源:互联网 发布:女孩26周四维彩超数据 编辑:程序博客网 时间:2024/05/21 10:06
点击上方“程序员大咖”,选择“置顶公众号”
关键时刻,第一时间送达!
在掌握 Python 生成器之前,你必须了解常规 Python 函数的工作原理。通常,当一个 Python 函数调用子程序(subroutine)时,这个子程序将一直持有控制权,只有当子程序结束(返回或者抛出异常)后,控制权才还给调用者:
>>>deffoo():
... bar()
...
>>>defbar():
... pass
标准的 Python 解释器是用 C 写的。解释器用一个叫做 PyEval_EvalFrameEx 的 C 函数来执行 Python 函数。它接受一个 Python 的堆栈帧(stack frame)对象,并在这个堆栈帧的上下文中执行 Python 字节码。这是 foo 的字节码:
>>>importdis
>>>dis.dis(foo)
2 0LOAD_GLOBAL 0(bar)
3CALL_FUNCTION 0(0positional,0keywordpair)
6POP_TOP
7LOAD_CONST 0(None)
10RETURN_VALUE
foo 函数将 bar 加载到堆栈中并调用它,然后从堆栈中弹出返回值,最后加载并返回 None。
当 PyEval_EvalFrameEx 遇到 CALL_FUNCTION 字节码的时候,它会创建一个新的 Python 堆栈帧,然后用这个新的帧作为参数递归调用 PyEval_EvalFrameEx 来执行 bar。
Python 的堆栈帧是分配在堆内存中的,理解这一点非常重要!Python 解释器是个普通的 C 程序,所以它的堆栈帧就是普通的堆栈。但是它操作的 Python 堆栈帧是在堆上的。除了其他惊喜之外,这意味着 Python 的堆栈帧可以在它的调用之外存活。(FIXME: 可以在它调用结束后存活)。要以交互方式查看,请从 bar 内保存当前帧:
>>>importinspect
>>>frame = None
>>>deffoo():
... bar()
...
>>>defbar():
... globalframe
... frame = inspect.currentframe()
...
>>>foo()
>>># The frame was executing the code for 'bar'.
>>>frame.f_code.co_name
'bar'
>>># Its back pointer refers to t
>>>defbar():
... globalframe
... frame = inspect.currentframe()he frame for'foo'.
>>>caller_frame = frame.f_back
>>>caller_frame.f_code.co_name
'foo'
现在这项技术被用到了 Python 生成器(generator)上——使用代码对象和堆栈帧这些相同的组件来产生奇妙的效果。
这是一个生成器函数(generator function):
>>>defgen_fn():
... result = yield1
... print('result of yield: {}'.format(result))
... result2 = yield2
... print('result of 2nd yield: {}'.format(result2))
... return'done'
...
当 Python 将 gen_fn 编译为字节码时,它会看到 yield 语句,然后知道 gen_fn 是个生成器函数,而不是普通函数。它会设置一个标志来记住这个事实:
>>># The generator flag is bit position 5.
>>>generator_bit = 1 << 5
>>>bool(gen_fn.__code__.co_flags & generator_bit)
True
当你调用一个生成器函数时,Python 会看到生成器标志,实际上并不运行该函数,而是创建一个生成器(generator):
>>>gen = gen_fn()
>>>type(gen)
<class'generator'>
Python 生成器封装了一个堆栈帧和一个对生成器函数代码的引用,在这里就是对 gen_fn 函数体的引用:
>>>gen.gi_code.co_name
'gen_fn'
调用 gen_fn 产生的所有生成器都指向同一个代码对象,但是每个都有自己的堆栈帧。这个堆栈帧并不存在于实际的堆栈上,它在堆内存上等待着被使用
堆栈帧有个 “last instruction”(FIXME: translate this or not?) 指针,指向最近执行的那条指令。刚开始的时候 last instruction 指针是 -1,意味着生成器尚未开始:
>>>gen.gi_frame.f_lasti
-1
当我们调用 send 时,生成器达到第一个 yield 处然后暂停执行。send 的返回值是 1,这是因为 gen 把 1 传给了 yield 表达式:
>>>gen.send(None)
1
现在生成器的指令指针(instruction pointer)向前移动了 3 个字节码,这些是编译好的 56 字节的 Python 代码的一部分:
>>>gen.gi_frame.f_lasti
3
>>>len(gen.gi_code.co_code)
56
生成器可以在任何时候被任何函数恢复执行,因为它的堆栈帧实际上不在堆栈上——它在堆(内存)上。生成器在调用调用层次结构中的位置不是固定的,它不需要遵循常规函数执行时遵循的先进后出顺序。生成器被是被解放了的,它像云一样浮动。
我们可以将 “hello” 发送到这个生成器中,它会成为 yield 表达式的值,然后生成器会继续执行,直到产出(yield)了 2:
>>>gen.send('hello')
result of yield:hello
2
现在这个生成器的堆栈帧包含局部变量 result:
>>>gen.gi_frame.f_locals
{'result':'hello'}
从 gen_fn 创建的其他生成器将具有自己的堆栈帧和局部变量。
当我们再次调用 send 时,生成器将从它第二个 yield 处继续执行,然后以产生特殊异常 StopIteration 结束:
>>>gen.send('goodbye')
resultof2ndyield:goodbye
Traceback(most recent call last):
File"<input>",line1,in <module>
StopIteration:done
异常有一个值,它是那个生成器的返回值:字符串 “done”。
翻译:你逗比
segmentfault.com/a/1190000011330511
程序员大咖整理发布,转载请联系作者获得授权
【点击成为安卓大神】
- Python 生成器原理详解
- Python 生成器原理详解
- 只要3分钟,Python生成器原理详解
- python生成器详解
- Python 生成器详解 generator
- python生成器详解
- python 迭代器与生成器 详解
- Python生成器(Generator)详解
- Python生成器(Generator)详解
- python 迭代器与生成器 详解
- Python生成器(Generator)详解
- python中的迭代器与生成器详解
- 详解Python中yield生成器的用法
- 详解Python中yield生成器的用法
- Python 生成器
- Python生成器
- python生成器
- python 生成器
- 2018 届互联网校招高薪清单曝光:25 万年薪只是白菜价?
- Java LinkedList 源码分析
- 普通人和程序员躺在床上时的对比
- Android 应用开发集成开发环境 Android Studio
- Python 超 Java 排第二!GitHub 2017 年度报告~
- Python 生成器原理详解
- 完全平方数
- 进入信息与计算科学这个专业后我的感受
- Spark之java操作WordCount
- 如何禁止(表单)用户名、密码自动填充
- OpenRTB 3.0的热寂
- Sql Server 系统的存储过程
- Yarn 内存分配管理机制及相关参数配置
- Bootstrap-Tree