Python面试_可迭代对象&迭代器&生成器
生成器
生成器函数
- 生成器函数:函数体内有yield关键字
- 函数返回值:生成器对象
- 函数执行顺序:在生成器对象每次调用next()时执行,遇到yield语句返回,再次调用执行时从上次返回的yield语句处继续执行
def generator_func():
print("step 1")
yield 1
print("step 2")
yield 2
print("step 3")
yield 3
def normal_func():
return 1
if __name__ == '__main__':
g = generator_func() # <generator object generator_func at 0x7ff0908cade0>
n = normal_func() # n: 1
# print(dis.dis(generator_func))
for i in g:
print("generator obj res: ", i)
"""
step 1
generator obj res: 1
step 2
generator obj res: 2
step 3
generator obj res: 3
"""
函数执行
从下面的例子中,让我们来了解函数的执行过程:
import inspect
frame = None
def foo():
bar()
def bar():
global frame
frame = inspect.currentframe() # 返回当前栈帧对象
foo()
print(frame.f_code.co_name) # 打印栈帧对象名 --> bar
caller_frame = frame.f_back # 获取调用栈帧对象
print(caller_frame.f_code.co_name) # 打印调用栈帧对象名 --> foo
python中函数调用的执行过程:
1)python.exe会通过C实现的函数:PyEval_EvalFrameEx()去执行定义的Python foo()函数
- 首先会创建一个栈帧对象(FrameObject)
- 栈帧对象会去执行foo()函数的Python字节码
2)当执行到调用bar()函数时
- 会再创建一个栈帧对象(FrameObject)
- 新的栈帧对象会去执行bar()函数的Python字节码
流程如图:
Python中函数的调用类似递归的过程,所有的堆栈对象都分配在堆内存上(不会随着函数执行完而销毁),这就决定了堆栈对象可以独立于调用者而存在。
生成器函数执行
CPython解释器在编译Python字节码时就会生产生成器对象,C函数PyEval_EvalFrameEx()会在堆栈对象的基础上进行封装为生成器对象(PyGenObject),生成器对象(PyGenObject),gi_frame指向一个新的PyFrameObject,其中保存了生成器对象上次执行的字节码位置(f_lasti),而gi_code则指向PyCodeObject用于保存生成器函数的字节码。
通过一个例子来看看其具体过程,首先通过dis来打印出生成器函数的Python字节码:
def generator_func():
yield 1
name = "zgt"
yield 2
age = 18
return "hello generator"
print(dis.dis(generator_func))
再通过next()函数来执行生成器对象,并打印其堆栈对象中内容:
g = generator_func()
print(g.gi_frame.f_lasti) # -1
print(g.gi_frame.f_locals) # {}
next(g)
print(g.gi_frame.f_lasti) # 2
print(g.gi_frame.f_locals) # {}
next(g)
print(g.gi_frame.f_lasti) # 12
print(g.gi_frame.f_locals) # {'name':'zgt'}
对比生成器函数的字节码,我们就可以理解Python的生成器的独特aazA处(延时求值)的实现原理。