【问题标题】:generator functions using the CPython api使用 CPython api 的生成器函数
【发布时间】:2018-08-26 21:55:01
【问题描述】:

我试图弄清楚如何使用 CPython api 在 C 中编写生成器函数。不幸的是,我不明白如何做,文档也没有很好地解释它。有人可以解释生成器函数如何在低级代码中工作以及我如何创建它们吗?

类似的东西

def gen_func(*args):
    for arg in args:
        yield arg

【问题讨论】:

  • 你真的需要创建一个生成器,还是可以接受任何迭代器?因为迭代器非常简单(然后这个问题是probably a dup)。同时,如果你想要一个生成器,它只是必须是一个返回生成器迭代器的函数,还是必须像生成器函数一样实际工作?
  • 我一直在考虑这个问题,我意识到迭代器很容易完成,并且可以通过PyRun_String 创建生成器,但真正的技巧是创建一个生成器函数,而不是迭代器和它必须像生成器函数一样工作
  • 在这种情况下,您可能想看看 Cython 是如何做到的——尽管如果您不习惯阅读 Cython 生成的代码,这可能会很痛苦(当然,如果你不想处理send/等,不需要伪造回溯等)。创建一个更简单的生成器(def gen_func(): yield 1),将其保存在 .pyx 文件中,cythonize 它,然后读取.c 文件;函数本身应该有一个 __pyx_pf_[mod-name-mangle]_gen_func,而在 __next__sendclosethrow 方法附近的函数,但很多胆量将在 Cython 中。
  • 无论如何,我看到它的感觉是你必须手动完成所有事情 - __next__ 基本上是你为迭代器所做的一切,加上管理首次运行标志,以及其他方法也都得写。
  • 此外,即使返回简单迭代器的函数是不可接受的,您也应该阅读链接的问题(到目前为止,我只略略略过); OP 开始想要创建一个生成器函数,即使他最终对返回迭代器的函数感到满意。

标签: python c generator cpython


【解决方案1】:

首先,PyRun_String(或任何可以模拟evalexec 的东西)当然可以做到,但这似乎是作弊;您不是在 C 中构建生成器函数,而是在 Python 中构建一个,然后在 C 中调用它。

无论如何,您不知道如何使用 C API 构建生成器函数的原因是没有 C API 可以执行此操作。或者,更确切地说,有一个 C API 可以从运行 Python 生成器代码对象的 CPython 框架对象构建生成器(以及从生成器代码对象构建生成器函数,但您甚至可以从 Python 中完成这部分;它只是 types.FunctionType构造函数),但这对你没有任何好处。 (除非你只是想编写 C 代码来为生成器构建 Python 字节码,这与 PyRun_String 一样作弊,还有更多工作。)

因此,如果您想在 C 中构建生成器函数,则必须手动完成。显然可以做到这一点,正如 Cython 可以做到的事实所证明的那样(在某些限制下 - 例如,inspect.isgeneratorfunctioninspect.isgenerator 将在 gen_funcgen_func() 上返回 False)。但这并不容易,我不确定它会给你带来什么。

核心问题是 CPython 通过冻结 CPython 帧并传递它们来实现生成器(因此是 API)。 C 代码不使用 CPython 框架,它使用 C 堆栈。 (即使您使用 setjmp/longjmp 和显式堆栈复制来构建 C 协程,您也会与 CPython 本身使用 C 堆栈的方式作斗争。)

所以,我能想到的唯一可行的选择是构建一个迭代器类(this answer 显示了如何做),然后在此之上实现生成器协议的其余部分。这与在 Python 中实现生成器协议基本相同,但将状态存储在 PyObject 结构而不是对象字典中,就像将任何其他类转换为 C 一样。

如果你想看看 Cython 做了什么,基本上就是这样,尽管你必须翻阅很多样板才能看到它。创建文件genpyx.pyx

def gen_func():
    yield None

然后cythonize genpyx.pyx,并查看创建的genpyx.c 文件。 (寻找__pyx_gb_6genpyx_2generator,以及它附近的大多数其他东西。)尽管 Cython 具有部分伪造帧的机制,因此它可以通过 Cython 代码在任一端使用 Python 进行回溯,但它仍然将所有状态显式存储在一个结构,它通过函数传递,就像你必须做的那样。 Cython 确实支持两个准文档化的生成器属性 gi_runninggi_yieldfrom,这是一个好主意,但它不能伪造 gi_framegi_code(除了扩展函数试图伪造 __code__ ),并且它不会伪装成 types.GeneratorType 的实例(您可以这样做,但它与任何其他非堆类型一样危险)。

同时,如果您的模拟生成器没有任何使用 yield 的值,那么实现一个接受并忽略一个参数的 send 有什么意义,检查一个否则不必要的首次运行标志是设置,然后执行与__next__ 相同的操作?如果您正在构建 Cython 并且需要一些东西来编译 Cython 生成器主体,那么尝试实现尽可能多的生成器协议是必要的,但如果您只是手动翻译 gen_func 之类的东西,则需要 YAGNI。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-23
    • 2022-08-09
    • 2012-01-13
    • 2021-03-25
    • 2018-11-07
    • 2015-05-25
    相关资源
    最近更新 更多