以下内容基于python3.4
1. python中的普通函数是怎么运行的?
当一个python函数在执行时,它会在相应的python栈帧上运行,栈帧表示程序运行时函数调用栈中的某一帧。想要获得某个函数相关的栈帧,则必须在调用这个函数且这个函数尚未返回时获取,可能通过inspect模块的currentframe()函数获取当前栈帧。
栈帧对象中的3个常用的属性:
- f_back : 调用栈的上一级栈帧
- f_code: 栈帧对应的c
- f_locals: 用在当前栈帧时的局部变量;
比如:
>>> import inspect >>> def func(): ... global x ... x = inspect.currentframe() ... >>> x = None >>> func() >>> x <frame object at 0x7f50f3ee2868>
更进一步讲, 标准的python解释器是用C语言写的,通常称作CPython, 当执行一个python函数时,解释器中的C函数 PyEval_EvalFrameEx() 就会被调用,它来处理python 代码的字节码, 它的参数为对于python函数的栈帧 object,即上面例子中的 x就是一个栈帧对象。
举例说明函数是如何运行的?
>>> def foo(): ... x = 12 ... y = bar() ... return y ... >>> def bar(): ... return ‘hello‘ ...
使用dis模块查看一下函数foo()的字节码(看不懂内容没事,其它有规律):
>>> import dis >>> dis.dis(foo) 2 0 LOAD_CONST 1 (12) 3 STORE_FAST 0 (x) 3 6 LOAD_GLOBAL 0 (bar) 9 CALL_FUNCTION 0 (0 positional, 0 keyword pair) 12 STORE_FAST 1 (y) 4 15 LOAD_FAST 1 (y) 18 RETURN_VALUE
运行过程:
解释器调用 C函数 PyEval_EvalFrameEx()运行foo()的字节码,它的参数为foo()对应的栈帧对象,运行位置为foo()对应的栈帧; 在运行过程中,遇到 CALL_FUNCTION 时,它会为函数bar()生成新的栈帧,然后又调用一个 PyEval_EvalFrameEx() 运行bar()对应的字节码,……,如此递归,然后一层层的返回;
2. 对于python中栈帧:
在python中的栈帧其实是在解释器的堆上分配内存的,所以,在一个python函数运行完成后,它的栈帧的仍然存在,并没有消失,下面例子说明了(当func函数运行完成后,我们然后可以访问到它对应的栈帧):
>>> import inspect >>> def func(): ... global x ... x = inspect.currentframe() ... >>> x = None >>> func() >>> x <frame object at 0x7f50f3ee2868> >>> x.f_code.co_name ‘func‘
3. python中的生成器函数是怎么运行的?
#这是一个函数 >>> def func(): ... print(‘You are SB‘) ... #这是一个生成器
>>> def gen(): ... yield ‘You are SB‘ ... return ‘ni gei wo gun‘
对于函数与生成器函数的区别在于生成器中有yield表达式, 它们的co_flags是不相同的:
function没有*args或**kw时,func.__code__.co_flags=67; function有*args没有**kw时,func.__code__.co_flags=71;
function没有*args有**kw时,func.__code__.co_flags=75; function既有*args也有**kw时,func.__code__.co_flags=79;
function是一个generator时,func.__code__.co_flags=99.
>>> func.__code__.co_flags 67 >>> gen.__code__.co_flags 99
当运行一个生成器函数时,它会生成一个生成器:
>>> a = gen() >>> type(a) <class ‘generator‘> >>> b= gen() >>> b <generator object gen at 0x7f50f4a7a3f0>
上面例子中生成了两个生成器a与b, 每一个生成器都有两个常用的属性,分别为gi_frame与gi_code, 不同的生成器的gi_code是相同的,对应生成器函数的字节码,然而它们的gi_frame是不相同的,所以,不同的生成器可以分别运行,并且互不干扰;
对于每一个栈帧又都有一个指针f_lasti,它指向了最后执行的命令,在一开始没有执行时,它的值为-1;
>>> a.gi_frame.f_lasti -1 >>> a.send(None) ‘You are SB‘ >>> a.gi_frame.f_lasti 3 >>> b.gi_frame.f_lasti -1
当生成器执行到最后时,它就产生一个 StopIteration
异常,然后就停止了,当生成器函数中有return时, 这个异常的值就是return的值,如果没有return,异常的值为空;
>>> next(b) ‘You are SB‘ >>> next(b) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration: ni gei wo gun
生成器函数就就是这么运行的。