我试图弄清楚如何使用CPython api在C中编写生成器函数。不幸的是,我不明白如何和文档不能很好地解释它。有人可以解释生成器函数如何在低级代码中工作以及我如何创建它们?
与
相似的东西def gen_func(*args):
for arg in args:
yield arg
答案 0 :(得分:1)
首先,PyRun_String
(或任何可以模拟eval
或exec
的东西)当然可以做到,但这似乎是作弊;你没有在C中构建生成器函数,你在Python中构建一个函数,然后在C中调用它。
无论如何,你无法弄清楚如何使用C API构建生成器函数的原因是没有C API来执行它。或者,更确切地说,有一个C API用于从运行Python生成器代码对象的CPython框架对象构建生成器(以及用生成器代码对象构建生成器函数,但是这部分甚至可以从Python执行;它只是{{1构造函数),但这对你没有任何好处。 (除非你只是想编写为生成器构建Python字节码的C代码,这会像types.FunctionType
那样作弊,而且工作量也会增加。)
因此,如果要在C中构建生成器函数,则必须手动执行。显然可以这样做,正如Cython可以做到的那样(达到某个限制 - 例如,PyRun_String
和inspect.isgeneratorfunction
将在inspect.isgenerator
上返回False
和gen_func
)。但这并不容易,我不确定它会给你带来什么。
核心问题是CPython通过冻结CPython帧并传递它们来实现生成器(因此API)。 C代码不使用CPython框架,它使用C堆栈。 (即使您使用gen_func()
/ setjmp
和显式堆栈复制来构建C协同程序,您也会采用CPython本身使用C堆栈的方式。)
所以,唯一可行的选择我可以想到它来构建一个迭代器类(this answer显示如何做),然后在其上实现其余的生成器协议。它与在Python中实现生成器协议基本相同,但是将您的状态存储在PyObject结构而不是对象dict中,就像将任何其他类转换为C一样。
如果你想看看Cython做了什么,基本上就是这样,虽然你必须通过大量的样板来看它。创建文件longjmp
:
genpyx.pyx
然后def gen_func():
yield None
,查看创建的cythonize genpyx.pyx
文件。 (寻找genpyx.c
,以及它附近的大多数其他东西。)尽管Cython有一个部分伪造帧的机制,所以它可以通过Cython代码用两端的Python进行回溯,它仍然存储所有的状态显式地在一个结构中,它通过函数传递,就像你必须的那样。 Cython确实支持两个准文档生成器属性__pyx_gb_6genpyx_2generator
和gi_running
,这是一个不错的主意,但它不能伪造gi_yieldfrom
和gi_frame
(不超过扩展函数尝试伪造gi_code
),它并不伪造成__code__
的实例(你可以这样做,但它会像任何其他非堆类型一样危险)
同时,如果你的模拟生成器没有任何使用types.GeneratorType
的值,那么实现一个yield
接受并忽略一个参数的重点是什么,检查一个否则是不必要的设置-run标志,然后执行与send
相同的操作?如果你正在构建Cython并且需要一些东西来编译Cython生成器主体,那么尝试尽可能多地实现生成器协议是必要的,但是如果你只是手动翻译__next__
之类的东西,则需要YAGNI。