如何使Cfunction指针成为另一个Cfunction和Python函数参数上的闭包(来自Python)

2024-09-27 23:22:29 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在处理一个有趣的难题——如何创建在运行时引用Python数据的C函数的闭包?这有点长,但是如果你喜欢编程难题,这里有一些技术上有趣的东西,所以值得一读

假设我们有:

def f(data): ... implementation...

然后

def make_closure(f, d):  # d is a data for f
  def g(): return f(d)
  return g

g是fd的闭包。我们在g内部有captured参数d,现在我们可以调用一个使用d的函数,而不需要提供d。这是Python和所有具有第一类数据类型函数的语言中的一块黄油

现在我的问题(版本1)。我正在使用(来自Python)一个接受回调的C库:

void register_func(callback_t g) {... }

该库不允许我将自己的任何数据传递给该回调,因此我需要创建一个C回调函数来关闭其数据。幸运的是,CFUNCTYPE为我们做了一些神奇的事情:

from ctypes import CFUNCTYPE

def make_closure(f, d):
  @CFUNCTYPE(...) 
  def g(): return f(d)

  return g

lib = ctypes.CDLL("thirdpartylib", mode=ctypes.RTLD_GLOBAL)
g = make_closure(f, data)   # Python function f, Python data object data
lib.register_func(g) # Call the C function, provide a Python function as the callback.

哇!这可能并不明显,但这里发生了神奇的事情在运行时我们生成了一个新的C风格函数指针(指向程序文本(机器代码)数据的指针),可由C调用,它引用动态创建的Python函数和数据。(!) 因此,我们动态创建了一个Python数据的闭包,可由C.Magic调用

问题解决了吗?别那么快。我的实际问题(版本2)有点棘手

使用第三方C库,API:

result_t callback(int iterstatus);   // Prototpe of function callback_t

void register_iterator(char* name, callback_t callback);

库将执行注册的迭代器,大致如下(用C实现):

// Initialize the iterator's state (iterstatus==0)
context = callback(0, NULL)

// Process each result of the iterator (iterstatus==1)
while (true){
  result = callback(1, context);
  if (result) yield_data(result);
}

// Cleanup the iterator's state (iterstatus==2)
// free(context->user_data)
callback(2, context); 

(我知道——我们没有得到我们想要的API,只有我们拥有的)

因此,我可以使用提供的CFUNCTYPE对象作为回调register_iterator(name, g)的指针来注册回调。然而,Python在执行迭代部分时速度非常慢。实际上,我需要Python来创建迭代数据(一个numpy数组)并让C例程对其进行迭代。像这样:

/// Data that callback_closured need in its implementation
/// We be assigned a CFUNCTYPE value and point to a Python function
static py_iterator_allocator_t closure_alloc_func = NULL;

void* callback_closured(int iterstatus, iterstate_t* state){
   switch (iterstatus){
   case 0:
        assert(state==NULL);
        state = closure_alloc_func();   // Ask python to allocate the state
        return state;
    case 1:
        if(state->next==state->end) return NULL;  // no more data
        state->next++;
        return (state->next)-1;
    case 3:                           // Cleanup
        py_decref(state);             
    default:
        return NULL;
    } 
}

void my_register_iterator(char* name, callback_t callback, py_iterator_allocator_t alloc_func){
   closure_alloc_func = alloc_func;
   register_iterator(name, my_register_iterator);
}

。。。因此,在Python中,我们称之为:


@CFUNCTYPE(...) 
def alloc_iterator():  
   ...
   return pyobject;

lib.my_register_iterator(name, alloc_iterator)

。。。这是可行的,但很糟糕的是有很多方法(我们需要新的全局变量和一个新的my_register_iterator函数和closure_alloc_func以便它从C中引用我们想要注册的每个函数——我真的不想为每个新闭包编辑C源代码)

因此,问题是:

  • 我能否在运行时从python中为任何python分配器动态创建callback_closured函数指针
    // Python-land
    
    @CFUNCTYPE(...) 
    def alloc_iterator():  
       ...
       return pyobject;

    # lib.iter_impl has the prototype
    # void* iter_impl(int, iterstate_t*, alloc_funct_t )
    iter_impl2 = make_iterator_impl(lib.iter_impl, alloc_func);

    # iter_impl2 references alloc_func, somehow... and has the calling prototype and references 
    #void* (int, iterstate_t* state) is the new C prototype
    register_iterator(name, iter_impl2);

调用iter_impl2(int s, void* state),大致相当于

return iter_impl(s, state, alloc_func)

。。。动态创建的闭包,但在C中

好处是分配可以用Python(灵活的behvaior)实现,但迭代可以用C(fast)实现:

// C-land
void* iter_impl(int iterstatus, iterstate_t* state, alloc_funct_t alloc_func){
   switch (iterstatus){
   case 0:
        assert(state==NULL);
        state = alloc_func();   // Ask python to allocate the state
        return state;
    case 1:
        if(state->next==state->end) return NULL;  // no more data
        state->next++;
        return (state->next)-1;
    case 3:                           // Cleanup
        py_decref(state);             
    default:
        return NULL;
    } 
}

这似乎并不琐碎。但由于ctypes似乎能够生成作为Python数据闭包的C可调用函数指针,但这似乎并非不可能。 Ctypes似乎发明了指向程序文本内存的新函数指针。我所知道的在C中生成函数指针的唯一方法是:I)使用新名称声明和编译新函数,或者ii)加载共享库。所以,也许有一种方法可以弯曲ctypes(或其方法)来实现这一点

我看了看CFFI。它提供了一种在运行时编译和链接C代码的方法,但显然没有提供一种捕获可从该代码引用的Python数据指针的方法


Tags: the数据函数registerdatareturncallbacknull

热门问题