python - 复制生成器

标签 python python-2.7 clone generator coroutine

假设我有一个像这样的发电机

def gen():
    a = yield "Hello World"
    a_ = a + 1 #Imagine that on my computer "+ 1" is an expensive operation
    print "a_ = ", a_
    b = yield a_
    print "b =", b
    print "a_ =", a_
    yield b

现在让我们说我做
>>> g = gen()
>>> g.next()
>>> g.send(42)
a_ =  43
43

现在我们已经计算出a_ .现在我想像这样克隆我的生成器。
>>> newG = clonify(g)
>>> newG.send(7)
b = 7
a_ = 43
7

但我原来的g仍然有效。
>>> g.send(11)
b = 11
a_ = 43
11

具体来说,clonify获取生成器的状态,并复制它。我可以将我的发电机重置为旧的一样,但这需要计算 a_ .另请注意,我不想大量修改生成器。理想情况下,我可以从库中获取一个生成器对象,然后 clonify它。

注:itertools.tee不会工作,因为它不处理发送。

注意:我只关心通过放置 yield 创建的生成器函数中的语句。

最佳答案

Python 不支持克隆生成器。

从概念上讲,这应该是可以实现的,至少对于 CPython。但实际上,事实证明这是非常困难的。

在幕后,生成器基本上只是堆栈框架周围的包装器。*

帧对象本质上只是一个代码对象、一个指令指针(该代码对象的索引)、内置/全局/本地环境、异常状态以及一些标志和调试信息。

并且这两种类型都暴露于 Python 级别,** 以及它们需要的所有位。所以,它真的应该只是一个问题:

  • 创建一个框架对象就像 g.gi_frame ,但用本地人的副本而不是原始的本地人。 (所有用户级别的问题都归结为是浅拷贝、深拷贝还是上述之一加上递归克隆生成器。)
  • 从新的框架对象(及其代码和运行标志)中创建一个生成器对象。

  • 并且没有明显的实际原因,它不应该从它的位中构造一个帧对象,就像代码对象或大多数其他隐藏的内置类型一样。

    不幸的是,事实证明,Python 没有公开构建框架对象的方法。我以为你可以通过使用 ctypes.pythonapi 来解决这个问题调用 PyFrame_New ,但第一个参数是 PyThreadState ——你绝对不能从 Python 构建,也不应该能够。因此,要完成这项工作,您必须:
  • 重现一切 PyFrame_New通过 ctypes 敲击 C 结构来实现, 或
  • 人工造假 PyThreadState通过敲击 C 结构(这仍然需要仔细阅读代码到 PyFrame_New 才能知道你必须伪造什么)。

  • 我认为这仍然是可行的(我打算玩它;如果我想出什么,我会更新我博客上的 Cloning generators 帖子),但这绝对不是微不足道的——或者,当然,甚至远程便携。

    还有一些小问题。
  • 本地变量作为 dict 暴露给 Python(无论您是为自己调用 locals(),还是为要克隆的生成器访问 g.gi_frame.f_locals)。在幕后,局部变量实际上存储在 C 堆栈中。*** 你可以通过使用 ctypes.pythonapi 来解决这个问题。调用 PyFrame_LocalsToFast PyFrame_FastToLocals .但是 dict 只包含值,而不是单元格对象,因此执行此 shuffle 会将所有非局部变量转换为克隆中的局部变量。****
  • 异常状态作为类型/值/回溯 3 元组暴露给 Python,但在框架内还有对拥有生成器的借用(非引用计数)引用(如果它不是生成器框架,则为 NULL)。 ( The source 解释了原因。)因此,您的框架构造函数无法对生成器进行引用计数,或者您有一个循环,因此存在泄漏,但它必须对生成器进行引用计数,否则您可能有一个潜在的悬空指针,直到分配了帧到发电机。显而易见的答案似乎是在框架构造时将生成器保留为 NULL,并让生成器构造函数执行等效于 self.gi_f.f_generator = self; Py_DECREF(self) 的操作。 .


  • * 它还保留了框架的代码对象和运行标志的副本,以便在生成器退出并处理框架后可以访问它们。

    ** generatorframe对内置函数是隐藏的,但它们可以作为 types.GeneratorType 使用 types.FrameType .他们有文档字符串,在 inspect 中描述了他们的属性。模块等,就像函数和代码对象一样。

    *** 当你编译一个函数定义时,编译器会生成一个所有局部变量的列表,存储在 co_varnames 中。 ,并将每个变量引用转换为 LOAD_FAST/STORE_FAST带有索引的操作码进入 co_varnames作为其论据。当执行函数调用时,帧对象将堆栈指针存储在 f_valuestack 中。 , 推len(co_varnames)*sizeof(PyObject *)到堆栈上,然后 LOAD_FAST 0只需访问 *f_valuestack[0] .闭包更复杂;在对 SO 答案的评论中解释太多了。

    **** 我假设您希望克隆共享原始的闭包引用。如果您希望递归地克隆堆栈中的所有帧以获取一组新的闭包引用以进行绑定(bind),则会增加另一个问题:也无法从 Python 构造新的单元格对象。

    关于python - 复制生成器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29835784/

    相关文章:

    python - 无法对对象 : pymongo. 光标进行编码。光标对象位于

    python - 如果在 if 语句中使用外部函数的变量,为什么不能覆盖该变量的值?

    python 自动(或动态)导入包中的类

    python - 如何在Python中连续连接子列表中的一对坐标?

    python-2.7 - Mandrill python API 附件错误信息

    list - python中限制列表的大小

    jQuery : Click and clone bug

    python - Pandas - 将字符串类型转换为 float

    java - 深度复制图结构

    java - 数组克隆不起作用