Python C 扩展装饰器
首先,抱歉我说的有点多背景信息。
最近我在玩Python/C-API(Python 3.4),遇到了一些问题。我的目标是创建一个C扩展,可以用作函数或方法的装饰器。我有一个相对不错的原型,做了一个LRU缓存,具体内容可以在这个链接找到:https://gist.github.com/pbrady/916495198910e7d7c713。虽然这个缓存工作得很好,而且速度很快,但我遇到了两个问题:
我的装饰器返回的
type
不是函数,也就是说:>>> from lrucache import lrucache >>> @lrucache() ... def f(a, b): ... return a+b ... >>> type(f) >>> <class 'lrucache.cache'>
因此,这个装饰器在方法上不能正常工作——看起来
self
丢失了,因为Python在看到我的类时并没有创建实例方法(这也很合理)。把装饰函数的
__doc__
复制到我的缓存类中,并不会影响help
显示的信息。
为了解决这些问题,我的想法是简单地返回用户的函数,并做一些修改,而不是返回一个新的自定义类对象。这些修改包括:
给函数添加一个
__wrapped__
属性,并指向原函数。重写
__call__
属性,这样函数调用就会指向一个自定义的C例程。
我成功完成了第一个修改,但重写函数的__call__
方法没有任何效果,因为解释器一般不使用这个方法。
我的问题(终于来了): 我该如何创建一个Python函数(也就是一个PyFunctionObject),使其能够调用一个C函数?
或者,如果有更好的方法,我也很感兴趣。这个主要是一个学习和娱乐的练习,所以我对基于Cython的解决方案不是很感兴趣。
1 个回答
首先,你的第二步显然是失败的,因为特殊属性是从类中查找的,而不是从实例中查找的:
In [1]: class Test:
...: def __call__(self):
...: print('Called class attribute!')
...:
In [2]: t = Test()
In [3]: def new_call():
...: print('Called instance attribute!')
...:
In [4]: t.__call__ = new_call
In [5]: t()
Called class attribute!
而且你不想修改 function
类的 __call__
方法,因为这样会大幅改变 Python 代码的语义。
据我所知,创建一个 PyFunctionObject
的方法只有一个,那就是调用 PyFunction_New
。但这个方法的问题在于,它需要一个 PyCodeObject
作为参数,而要创建这样的对象,你可以调用:
PyCode_NewEmpty
:这个方法会创建一个 无效 的代码对象。它仅用于创建你不关心代码内容的框架对象。PyCode_New
:这个方法会创建一个字节码对象。这个方法有 14 个参数,其中一个是PyObject *code
,它应该是一个可读的缓冲区,里面包含字节码的二进制表示。Py_CompileStringObject
:这似乎是唯一合理的解决方案。基本上,这就是内置的compile
函数。
一旦你创建了代码对象,创建函数就很简单了。不过要注意,这样做不会带来任何性能上的提升,因为函数对象的主体是被解释执行的。
不过,还有其他解决方案可以解决你的问题:
你可以把你的
lrucache
做成一个 描述符。这就是 Python 对方法的处理方式,自动将self
作为第一个参数传递,所以你可以模仿这种行为。你可以写一个非常简单的 Python 模块,导入你的 C 扩展,并提供一个这样的函数:
from functools import wraps from _lrucache import cache def lrucache(func): cached_func = cache(func) @wraps(func) def wrapper(*args, **kwargs): return cached_func(*args, **kwargs) return wrapper
这样你就可以通过 Python 代码绕过整个问题。
你可以创建一个函数类型的子类,并返回那个对象。不过,如果 Python 在构建类时检查确切的类型,这可能就不行了,而且实现起来可能也不简单,因为解释器可能会假设函数对象有一些属性,而这些你需要提供。不,你不能对函数进行子类化...