文章目录
1 函数的执行流程
函数的执行需要对函数进行压栈的,什么是压栈呢,简而言之就是在函数执行时在栈中创建栈帧存放需要变量以及指针的意思。具体涉及的知识非常多,这里就已一个Python脚本简单进行分析。
def foo1(b, b1=3):
print('call foo1', b, b1)
def foo2(c):
foo3(c)
print('call foo2', c)
def foo3(d):
print('call foo3', d)
def main():
print('call main')
foo1(100, 101)
foo2(20)
print('main ending')
main()
当我们运行上面代码时,它的执行流程如下:
- 全局栈帧中生成foo1、foo2、foo3、main函数对象
- main函数调用
- main中查找内建函数print压栈,将常量字符串压栈,调用函数,弹出栈顶
- main中全局查找函数foo1压栈,将常量100、101压栈,调用函数foo1,创建栈帧。print函数压栈,字符串和变量b、b1压栈,调用函数,弹出栈顶,返回值。
- main中全局查找foo2函数压栈,将常量200压栈,调用foo2,创建栈帧。foo3函数压栈,变量c引用压栈,调用foo3,创建栈帧。foo3完成print函数调用返回。foo2恢复调用,执行print语句后,返回值。main中foo2调用结束后弹出栈顶,main继续执行print函数调用,弹出栈顶,main函数返回
1.1 字节码了解压栈过程
Python 代码先被编译为字节码后,再由Python虚拟机来执行字节码, Python的字节码是一种类似汇编指令的中间语言, 一个Python语句会对应若干字节码指令,虚拟机一条一条执行字节码指令, 从而完成程序执行。Python dis 模块
支持对Python代码进行反汇编, 生成字节码指令。下面针对上面的例子通过字节码理解函数调用时的过程。
import dis
print(dis.dis(main))
# ======> result
53 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('call main')
4 CALL_FUNCTION 1
6 POP_TOP
54 8 LOAD_GLOBAL 1 (foo1)
10 LOAD_CONST 2 (100)
12 LOAD_CONST 3 (101)
14 CALL_FUNCTION 2
16 POP_TOP
55 18 LOAD_GLOBAL 2 (foo2)
20 LOAD_CONST 4 (20)
22 CALL_FUNCTION 1
24 POP_TOP
56 26 LOAD_GLOBAL 0 (print)
28 LOAD_CONST 5 ('main ending')
30 CALL_FUNCTION 1
32 POP_TOP
34 LOAD_CONST 0 (None)
36 RETURN_VALUE
字节码含义:
LOAD_GLOBAL
:加载全局函数(print)LOAD_CONST
: 加载常量CALL_FUNCTION
: 函数调用POP_TOP
:弹出栈顶RETURN_VALUE
: 返回值
1.2 嵌套函数的压栈
def outer():
c = 100
def inner():
nonlocal c
c += 200
return c
return inner
a = outer()
a()
- 函数只有在执行的时候才会压栈,所以在outer执行时,会开辟栈空间压栈(c,inner)
- 执行完后,删除栈空间,但是由于outer返回了内部函数inner,但并没有执行,所以不会继续压栈,当执行a的时候,会重新压栈,而此时内部函数已经记住了外部自由变量
c,并且每次调用outer都会重新生成一个inner。
注意:这种情况叫做闭包,自由变量c会被当成内部函数inner的一个属性,被调用。
PS:内存两大区域(栈,堆)。垃圾回收,清理的是堆中的空间。函数的调用就是压栈的过程,而变量的创建都是在堆中完成的。 栈中存储的都是堆中的内存地址的指向,栈清空,并不会使堆中的对象被清除,只是指向已经被删除。函数,变量都是在堆内创建的