一个生成器函数内部是如何工作的?

8
下面是一个生成器函数。
def f():
   x=1
   while 1:
      y = yield x
      x += y

这个生成器函数 (f) 是否会按照下面的方式进行内部实现?

class f(collections.Iterable):
   def __init__(self):
      self.x = 1
   def __iter__(self):
      return iter(self)
   def __next__(self):
      return self.x
   def send(self, y):
      self.x += y
      return self.next()

编辑:

这个 是我问题的答案。


1
你可以自己测试它们是否行为相同。深入到内部实现细节似乎对于一个SO问题来说过于宽泛。 - jonrsharpe
1
这篇文章可能会提供一些信息:http://aosabook.org/en/500L/a-web-crawler-with-asyncio-coroutines.html - Christian Dean
2
当然它们不是等价的,一个是类,一个是函数。 - wim
1
可能是[“yield”关键字是什么?]的重复问题(https://dev59.com/yXVC5IYBdhLWcg3woSpW) - pppery
2
简短的回答是,生成器在内部的实现方式与您的纯Python类所展示的不同。相反,它们与常规函数共享大部分相同的逻辑。 - Raymond Hettinger
显示剩余4条评论
1个回答

29

在内部,生成器的工作方式与常规函数调用大致相同。 在幕后,运行生成器和运行函数大多使用相同的机制。

当您调用函数或生成器时,将创建一个堆栈帧。 它具有局部变量(包括传递给函数的参数),指向活动操作码的代码指针以及用于挂起try块,with块或循环的堆栈。

在普通函数中,执行立即开始。 当遇到 return 时,最终结果被保留,并释放堆栈帧以及它引用的所有内容。

生成器函数中,堆栈帧包装在一个生成器迭代器对象中并立即返回。 仅当通过 next(g) g.send(v) 调用生成器函数时,才会运行生成器函数中的代码。 遇到 yield 时,执行将暂停。

将生成器视为可以使用 yield 暂停并使用 g.next() 恢复的函数的一种方式。 堆栈帧保持活动状态,因此恢复正在运行的生成器比进行新函数调用要便宜得多,后者需要在每次调用时构建新框架。


1
yield 如何在内部暂停/挂起函数? - overexchange
3
@overexchange 执行生成器和函数都涉及运行当前操作码并更新代码指针。暂停只意味着停止这样做。恢复意味着继续这样做。跟我一起数,1、2、3,现在谈论其他事情,然后继续数4、5、6...你需要知道的就是最后一个数字。同样,堆栈帧保持函数的状态,您可以随时恢复或停止更新它。 - Raymond Hettinger
异步编程的基本构建块是能够暂停/恢复的任务。对于服务器端编程,通常任务在执行IO时被暂停。当你说暂停是为了停止,难道我们不是因为等待IO(比如HTTP GET请求)而暂停吗? - overexchange
8
我觉得你把这个想象得比实际复杂。调用“next(g)”的代码基本上可以简化为“PyEval_EvalFrameEx(gen->gi_frame, exc)”。就是这样,它只执行帧的当前状态(请参见在Python 2.7中清晰简单的“Objects/genobject.c”)。一个“yield”仅仅从该调用返回。相比之下,函数调用运行“PyEval_EvalCodeEx(...)”,它创建一个新的帧并像上面展示的那样执行它。请参见“Objects/funcobject.c”。 - Raymond Hettinger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接