python中函数调用的实质原理:
python解释器(即python.exe)其实是用C语言编写的, 在执行python代码时,实际上是在用一个叫做Pyeval_EvalFramEx(C语言的函数)去执行代码中的函数,(实际上python中的程序实际上是运行在C语言之上的),运行此函数的时候,首先会在内存的堆区创建一个栈帧(stack frame),python中一切皆对象,在栈帧中间将要执行的代码编译成为字节码对象。 然后在栈帧的上下文中去运行字节码,可以用dis.dis()函数查看函数的字节码。
import dis def foo(): bar() def bar(): pass print(dis.dis(foo)) 结果: 20 0 LOAD_GLOBAL 0 (bar) 2 CALL_FUNCTION 0 4 POP_TOP 6 LOAD_CONST 0 (None) 8 RETURN_VALUE None
字节码解释:当foo调用子函数bar时候,又会创建一个栈帧,然后将函数的控制权交给新的栈帧,然后去运行bar的字节码,然后就有了两个栈帧了。因为所有的栈帧都是分配在堆的内存上,(函数调用完毕不会被立即回收)这就决定了栈帧可以独立于调用者存在,(即foo即使不存在了退出了也没有关系,只要有指针指向bar的栈帧,就可以对其进行控制)具体看一下代码:foo()调用完毕了之后,由于全局变量指向了bar中的栈帧对象,所以print(frame.f_code.co_name)语句输出产生当前栈帧对象的对象名,即bar,然后caller_frame = frame.f_back语句将调用者(foo)的栈帧对象获取到,然后打印出来,即foo。
import inspect import dis frame = None def foo(): bar() def bar(): global frame frame = inspect.currentframe() foo() print(frame.f_code.co_name) caller_frame = frame.f_back print(caller_frame) 输出结果: bar foo
图解如下:heap(堆区), recurse(递归,图中意思即foo递归调用了bar)
堆区的PyFrameObject表示生成的栈帧对象,f_code表示执行函数的字节码,f_back表示调用者函数的字节码。
def gen_fun(): yield 'a' name = 'bobby1' yield 'b' age = 30 return 'frank' import dis gen = gen_fun() print(dis.dis(gen)) #打印gen函数的字节码 print(gen.gi_frame.f_lasti) print(gen.gi_frame.f_locals) next(gen) print(gen.gi_frame.f_lasti) print(gen.gi_frame.f_locals) next(gen) print(gen.gi_frame.f_lasti) print(gen.gi_frame.f_locals) 输出结果: 20 0 LOAD_CONST 1 ('a') 2 YIELD_VALUE 4 POP_TOP 21 6 LOAD_CONST 2 ('bobby1') 8 STORE_FAST 0 (name) 22 10 LOAD_CONST 3 ('b') 12 YIELD_VALUE 14 POP_TOP 23 16 LOAD_CONST 4 (30) 18 STORE_FAST 1 (age) 24 20 LOAD_CONST 5 ('frank') 22 RETURN_VALUE None -1 {} 2 {} 12 {'name': 'bobby1'}
真是因为有yield实现生成器函数,使得我们可以自由控制函数的运行于暂停,这个是协程实现的基础。
生成器的应用实例:用生成器函数读取一行的超大文件。
def yield_str(file, spilt): buf = '' while True: while spilt in buf: pos = buf.index(spilt) yield buf[:pos] buf = buf[pos + len(spilt):] chunk = file.read(100) if not chunk: yield buf break buf += chunk with open('txt.txt', encoding='utf-8') as file: for i in yield_str(file, ','): print(i)