How to clone function in cython? I getting SystemError: unknown opcode

How to clone function in cython? I getting SystemError: unknown opcode

在 cpython 中,这段代码可以工作:

import inspect
from types import FunctionType


def f(a, b):  # line 5
    print(a, b)


f_clone = FunctionType(
    f.__code__,
    f.__globals__,
    closure=f.__closure__,
    name=f.__name__
)

f_clone.__annotations__ = {'a': int, 'b': int}
f_clone.__defaults__ = (1, 2)
print(inspect.signature(f_clone))  # (a: int = 1, b: int = 2)
print(inspect.signature(f))  # (a, b)
f_clone()  # 1 2
f(1, 2)  # 1 2
try:
    f()
except TypeError as e:
    print(e)  # f() missing 2 required positional arguments: 'a' and 'b'

但是在 cython 中调用 f_clone 时,我得到:

XXX lineno: 5, opcode: 0
Traceback (most recent call last):
  ...
  File "test.py", line 5, in f  # line of f definitio
SystemError: unknown opcode

我需要它在每个 class 创建时创建 class __init__ 方法的副本并修改其签名,但保持原始 __init__ 签名不变。

编辑: 对复制对象的签名所做的更改不得影响运行时调用,并且只需要用于检查目的。

我相对相信这永远不会奏效。如果我是你,我会修改你的代码以优雅地失败以用于不可克隆的函数(可能只是使用原始的 __init__ 而不是替换它,因为这似乎是一种生成更漂亮的文档字符串的纯粹美容方法)。之后您可以向 Cython issue tracker 提交问题 - 但是 Cython 的维护者知道与 Python 的完全内省兼容性非常具有挑战性,因此可能不会非常感兴趣。

我认为您应该只处理错误而不是寻找解决方法的主要原因之一是 Cython 不是加速 Python 的唯一方法。例如,Numba 可以生成包含 JIT 加速代码的 classes,或者人们可以用 C 编写自己的函数(作为 C-API 函数,或者可能用 Ctypes 或 CFFI 包装)。在这些情况下,你相当脆弱的内省方法可能会崩溃。处理错误可以解决所有这些问题;虽然您可能需要针对每一个单独的解决方法,加上我没有想到的所有方法,以及将来开发的任何方法。


关于 Cython 函数的一些细节:目前 Cython 有一个名为 binding 的编译选项,可以在两种不同的模式下生成函数:

  • 具有 binding=False 的函数具有类型 builtin_function_or_method,具有最小内省能力,因此没有 __code____globals____closure__(或大多数其他)属性。

  • With binding=True 函数的类型为 cython_function_or_method。这提高了内省能力,因此提供了大部分预期的注释。然而,其中一些是无意义的默认值 - 特别是 __code____code__ 属性应该是 Python 字节码,但是 Cython 不使用 Python 字节码(因为它被编译为 C)。因此它只是提供了一个虚拟属性。

看起来 Cython 在编译 .py 文件和编译常规(非 cdef)class 时默认为 binding=True,给出了您报告的行为。但是,在编译 .pyx 文件时,它当前默认为 binding=False。在某些情况下,您可能还想处理 binding=False 案例。

确定尝试使用 cython_function_or_method__code__ 属性创建常规 Python 函数对象是行不通的,让我们看看其他一些选项:

>>> print(f)
<cyfunction f at 0x7f08a1c63550>
>>> type(f)()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot create 'cython_function_or_method' instances

因此您无法创建自己的 cython_function_or_method 并从 Python 填充它 - 该类型没有用户可调用的构造函数。

copy.copy 似乎有效,但实际上并没有创建新实例:

>>> import copy
>>> copy.copy(f)
<cyfunction f at 0x7f08a1c63550>

但是请注意,这具有完全相同的地址 - 它不是副本:

>>> copy.copy(f) is f
True

这时候我就没主意了。


我不太明白的是你为什么不使用 functools.wraps

@functools.wraps(f):
def wrapper(*args, **kwargs):
    return f(*args, **kwargs)

这会使用 f 中的大部分相关内省属性更新 wrapper,适用于两种类型的 Cython 函数(在某种程度上 - binding=False 案例没有提供太多有用的信息),并且应该也适用于大多数其他类型的功能。

我可能遗漏了一些东西,但它似乎比您复制代码对象的方案要安全得多。