首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

python中的生成器函数是如何工作的?

1. python中的普通函数是怎么运行的?

当一个python函数在执行时,它会在相应的python栈帧上运行,栈帧表示程序运行时函数调用栈中的某一帧。想要获得某个函数相关的栈帧,则必须在调用这个函数且这个函数尚未返回时获取,可能通过inspect模块的currentframe()函数获取当前栈帧。

栈帧对象中的3个常用的属性:

f_back : 调用栈的上一级栈帧

f_code: 栈帧对应的c

f_locals: 用在当前栈帧时的局部变量;

比如:

更进一步讲, 标准的python解释器是用C语言写的,通常称作CPython, 当执行一个python函数时,解释器中的C函数 PyEval_EvalFrameEx() 就会被调用,它来处理python 代码的字节码, 它的参数为对于python函数的栈帧 object,即上面例子中的 x就是一个栈帧对象。

举例说明函数是如何运行的?

使用dis模块查看一下函数foo()的字节码(看不懂内容没事,其它有规律):

运行过程:

解释器调用 C函数 PyEval_EvalFrameEx()运行foo()的字节码,它的参数为foo()对应的栈帧对象,运行位置为foo()对应的栈帧; 在运行过程中,遇到 CALL_FUNCTION 时,它会为函数bar()生成新的栈帧,然后又调用一个 PyEval_EvalFrameEx() 运行bar()对应的字节码,……,如此递归,然后一层层的返回;

2. 对于python中栈帧:

在python中的栈帧其实是在解释器的堆上分配内存的,所以,在一个python函数运行完成后,它的栈帧的仍然存在,并没有消失,下面例子说明了(当func函数运行完成后,我们然后可以访问到它对应的栈帧):

3. python中的生成器函数是怎么运行的?

对于函数与生成器函数的区别在于生成器中有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.

当运行一个生成器函数时,它会生成一个生成器

上面例子中生成了两个生成器a与b, 每一个生成器都有两个常用的属性,分别为gi_frame与gi_code, 不同的生成器的gi_code是相同的,对应生成器函数的字节码,然而它们的gi_frame是不相同的,所以,不同的生成器可以分别运行,并且互不干扰;

对于每一个栈帧又都有一个指针f_lasti,它指向了最后执行的命令,在一开始没有执行时,它的值为-1;

当生成器执行到最后时,它就产生一个 StopIteration 异常,然后就停止了,当生成器函数中有return时, 这个异常的值就是return的值,如果没有return,异常的值为空;

生成器函数就就是这么运行的。

4.生成器相关操作:

1.X.__ next__()方法和next()内置函数

当我们调用一个生成器函数时来生成一个生成器X时,这个生成器对象就会自带一个X.__ next__()方法,它可以开始或继续函数并运行到下一个yield结果的返回或引发一个StopIteration异常(这个异常是在运行到了函数末尾或着遇到了return语句的时候引起)。也可以通过python的内置函数next()来调用X.__ next__()方法,结果都是一样的;

2.生成器函数协议中的send()方法

在讲send()方法的时候,有必要了解一下next()或__next__()或send()语句执行时,生成器内的程序执行到了哪里暂停了。写一个很简单的函数,使用pdb调试一下:

在第7行设置一个断点

通过看上面的程序,我们知道,当next()或__next__()或send()语句执行时,在生成器里面的程序中它执行到 yiled value 这条语句, 它yield出来了一个value值,但是没有执行yiled value表达式 的返回值它就暂停了;

现在说说send()方法:从技术上讲,yield是一个表达式,它是有返回值的,当我们使用内置的next()函数或__next__方法时,默认yield表达式的返回值为 None,它使用send(value)方法时,它可以把一个值传递给生成器,使得yield表达式的返回值为send()方法传入的值; 当我们第一次执行send()方法时,我们必须传入None值,因为第一次执行时,还没有等待返回值的yield表达式(虽然 send()方法会执行下一条yield语句,但是上面已经说明了它在还没有来得及执行yiled value表达式 的返回值时它就暂停了)

定义一个gen.py文件,里面的内容为:

3.生成器函数中的return 语句:

当生成器运行到了return语句时,会抛出StopIteration的异常,异常的值就是return的值; 另外,即使return后面有yield语句,也不会被执行;

4.另外,一个生成器对象也有close方法与throw方法,可以使用它们提前关闭一个生成器或抛出一个异常;使用close方法时,它本质上是在生成器内部产生了一个终止迭代的GeneratorExit的异常;

5. 最后一个要讲的内容:yield from

这个是在python3.0以后新增加的内容,可以让生成器delegate另一个生成器;

1.举一个例子看看它是怎么往外 yield数据的???

这个例子我们明白了两点:1. 当我们调用主生成器caller时,遇到yield from 时,它就会停下来,运行子生成器的程序, yield出来的数据就是子生成器里的数据;2. yield from 表达式的返回值为子生成器的return的值;

2.举个例子看看它是怎么通过 send()方法往里传递数据的?

通过这个例子,我们明白了1点:当主生成器遇到yield from以后,我们通过 send()方法传入值最终传给了子生成器;

3.通过 yield from ,可以嵌套调用生成器,比如:

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200108A0OBHU00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券