Python是如何实现生成器的原理

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)
原文地址:https://www.cnblogs.com/yc3110/p/10458663.html