【问题标题】:How to clone function in cython? I getting SystemError: unknown opcode如何在 cython 中克隆函数?我收到 SystemError: unknown opcode
【发布时间】:2019-11-27 14:53:30
【问题描述】:

在 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

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

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

【问题讨论】:

  • 我认为这可能行不通。 Cython 函数类型与 types.FunctionType 不同。 __code__ 在很大程度上是一个虚拟对象,只是为了给它一个属性。更改 __defaults__ 看起来不会影响调用的函数。我建议你要么放弃 Cython 来做这个程序,要么着眼于寻找一种截然不同的方法来实现这个功能(继承?创建一个委托给 __init__ 的新函数?)
  • 实际上,我不希望更改影响运行时的函数调用。只需按原样复制它并声明指向假类型函数的__wrapped__ 属性。该部分有效,但无法再调用复制的函数
  • 您可能想查看问题的原始上下文:github.com/samuelcolvin/pydantic/pull/1034
  • 这里有些奇怪,因为内置函数(以及 Cython 生成的 def-functions 被视为这样)没有__code__,所以你应该无法创建f_clone .但我不确定以上是否适用于所有版本的 Python/Cython 版本 - 所以也许你应该分享你如何构建扩展以及你使用的版本。
  • @ead 带有 Cython 选项 binding=True 它们是具有大多数正确属性的对象(尽管 __code__ 是一个虚拟对象等)。我认为这个选项很快就会成为默认选项。我假设 OP 正在使用它(否则它会更早中断,如你所说)。然而,没有属性的“旧” Cython 版本可能也是他们必须处理的事情

标签: python metaprogramming cython opcode


【解决方案1】:

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

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


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

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

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

看起来 Cython 在编译 .py 文件和编译常规(非 cdef)类时默认为 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 案例没有提供太多有用的信息),并且应该适用于大多数其他类型的函数也是如此。

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

【讨论】:

  • 谢谢!这实际上是我最终的结果:用新功能包装原始功能。我在 python 中没有考虑到这一点,因为它会添加对堆栈的冗余调用,并且可能会使用户在调试时感到困惑。然而,这在 cython 中不是问题,在调试时您无法介入其中的代码
  • 我认为冗余调用的成本会相当低,特别是对于像构造函数这样通常只不经常使用的东西。我也不认为堆栈上的额外项目太令人困惑 - 异常在正确的位置引发,并且使用像 init_wrapper 这样的合理名称,很明显发生了什么。无论如何 - 这是我的意见,但你比我更了解你编写的代码
猜你喜欢
  • 2022-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-10
  • 1970-01-01
  • 2021-11-18
相关资源
最近更新 更多