我正在处理一个有趣的难题——如何创建在运行时引用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是f
和d
的闭包。我们在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源代码)
因此,问题是:
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数据指针的方法
目前没有回答
相关问题 更多 >
编程相关推荐