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
案例没有提供太多有用的信息),并且应该也适用于大多数其他类型的功能。
我可能遗漏了一些东西,但它似乎比您复制代码对象的方案要安全得多。
在 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
案例没有提供太多有用的信息),并且应该也适用于大多数其他类型的功能。
我可能遗漏了一些东西,但它似乎比您复制代码对象的方案要安全得多。