假设我有一个像这样的发电机
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 帖子),但这绝对不是微不足道的——或者,当然,甚至远程便携。
还有一些小问题。
locals()
,还是为要克隆的生成器访问 g.gi_frame.f_locals
)。在幕后,局部变量实际上存储在 C 堆栈中。*** 你可以通过使用 ctypes.pythonapi
来解决这个问题。调用 PyFrame_LocalsToFast
和 PyFrame_FastToLocals
.但是 dict 只包含值,而不是单元格对象,因此执行此 shuffle 会将所有非局部变量转换为克隆中的局部变量。**** self.gi_f.f_generator = self; Py_DECREF(self)
的操作。 . * 它还保留了框架的代码对象和运行标志的副本,以便在生成器退出并处理框架后可以访问它们。
**
generator
和 frame
对内置函数是隐藏的,但它们可以作为 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/