Python C扩展装饰器

时间:2014-06-20 04:46:29

标签: python c decorator python-c-api

我提前为冗长的背景信息道歉。

我最近一直在使用Python / C-API(Python 3.4)并且已经陷入困境。我的目标是拥有一个C扩展,我可以将其用作函数/方法装饰器。我在gist https://gist.github.com/pbrady/916495198910e7d7c713中有一个相当好的LRU缓存原型。尽管缓存有效但速度非常快,但存在两个问题:

  1. 我的decorater返回的type不起作用:即

    >>> from lrucache import lrucache
    >>> @lrucache()
    ... def f(a, b):
    ...    return a+b
    ...
    >>> type(f)
    >>> <class 'lrucache.cache'>
    

    因此,装饰器在方法上无法正常工作 - 似乎self丢失了,因为Python在看到我的类时没有制作实例方法(这是有意义的)。

  2. __doc__从已修饰函数复制到我的缓存类不会影响help显示的消息。

  3. 解决这些问题的想法是简单地通过一些修改而不是新的自定义类对象来返回用户函数。这些修改是

    1. 在函数中添加__wrapped__属性并将其指向函数。

    2. 覆盖__call__属性,以便将函数调用定向到自定义C例程。

    3. 我能够完成(1)但是覆盖函数__call__方法没有做任何事情,因为解释器通常不会使用它。

      我的问题(最后): 如何创建一个调用C函数的Python函数(即PyFunctionObject)?

      或者,如果有更好的方法,我也会对此感兴趣。这主要是一个学习/有趣的练习,所以我对基于Cython的解决方案不太感兴趣。

1 个答案:

答案 0 :(得分:4)

你的(2)步骤明显失败,因为在类上查找特殊属性,而不是在实例上查找:

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设为descriptor。这就是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在构建类时检查确切的类型,这可能不起作用,并且实现可能不是微不足道的,因为解释器可能会假设您必须提供的有关函数对象的一些属性。不,你不能将函数子类化...