如何在 Python 中将“托管缓冲区”转换为 Callable

Posted

技术标签:

【中文标题】如何在 Python 中将“托管缓冲区”转换为 Callable【英文标题】:How to convert a 'managedbuffer' into a Callable in Python 【发布时间】:2018-06-07 14:39:36 【问题描述】:

我使用 SWIG 为 C++14 库编写了 Python Wrapper。 在 C++ API 中,我可以将 std::functions 注册为回调。

我有一个用于 std::function 的 SWIG 类型映射来传递 lambda 表达式 它调用 Python 回调:

%typemap(in) std::function 
    auto callback = [$input](auto&&... params) 
       PyGILState_STATE state = PyGILState_Ensure();

       PyObject* result =  PyObject_CallFunctionObjArgs($input,makePyObject(std::forward<decltype(params)>(params))..., NULL);
       const int retVal = PyObject_IsTrue(result);

       Py_DECREF(result);
       PyGILState_Release(state);
       return retVal == 1;
   ;
   $1 = std::move(callback);

当我运行测试脚本时,以下 Python 表达式有效 很好:

callback = lambda a,b: self.doStuff(a,b)
self.cppInterface.registerFunc(callback)

但是这个表达式不起作用:

self.cppInterface.registerFunc(lambda a,b: self.doStuff)

当我将 lambda 直接传递给 register 函数时, 从 C++ 调用回调时出现以下错误:

TypeError: 'managedbuffer' object is not callable

为什么 PyObject $input 不是可调用的? 如何允许两个 Python 表达式?

示例代码:

https://github.com/nullmedium/python-swig-demo

【问题讨论】:

“不起作用”是什么意思?你从类型映射中得到一个 TypeError ?您为此使用什么类型图? (你能在 MCVE 中证明这一点吗?) 我收到以下错误:TypeError: 'managedbuffer' object is not callable 我怀疑 Python 对这两个表达式的处理方式非常不同,$input PyObject 的处理方式必须不同。但我不知道如何从 managedbuffer Type 获取到可调用函数。 我怀疑这不仅仅是表面上看到的,可能是$input 所有权的引用计数问题。这里唯一明显的区别是两个 Python lambda 对象的生命周期。因此,当 lambda 被销毁时,您需要一种调用 Py_INCREF($input);Py_DECREF($input); 的方法,但我无法从您在此处显示的内容中证明这一点。 (老实说有点令人沮丧) 我只是尝试重新创建一个简单的示例(在家里),但在 MacOS 上,问题有点不同。 callback = lambda a,b: self.doStuff(a,b) 表达式不起作用,因为在 self dict 中找不到 doStuff。 【参考方案1】:

看起来您遇到了引用计数问题。您需要保留对 $input 的引用,即使在 std::function 类型映射完成之后也是如此。否则,一旦完成对registerFunc 的调用,它将丢失引用。最简单的方法是让你的 typemap 捕获 std::shared_ptr 而不是原始的 PyObject,例如:

%typemap(in) std::function 
    Py_INCREF($input);
    static const auto decref = [](PyObject *o) 
        Py_DECREF(o); // This needs to be another lambda/function because Py_DECREF is really a macro
    ;
    std::shared_ptr<PyObject> callable($input, decref);
    auto callback = [callable](auto&&... params) 
       PyGILState_STATE state = PyGILState_Ensure();
                                                      // Back to raw PyObject    
       PyObject* result =  PyObject_CallFunctionObjArgs(callable.get(),makePyObject(std::forward<decltype(params)>(params))..., NULL);
       int retVal = -1;

       if (result)
       
           retVal = PyObject_IsTrue(result);
           Py_DECREF(result);
       

       PyGILState_Release(state);
       return retVal == 1;
   ;
   $1 = std::move(callback);

理想情况下,我会使用std::unique_ptr,但即使使用generalised lambda captures,它也会阻止复制构造,并强制 SWIG 生成代码而不需要更多的工作。

不过,我也可能会在类型图中至少添加一个 PyCallable_Check 以作为衡量标准。

【讨论】:

谢谢!这确实解决了问题。我想我将创建一个 RAII 结构来调用 c'tor 中的 Py_INCREF 和 d'tor 中的 Py_DECREF 并将其传递给 lambda。我已经为 GIL 州做了一个。

以上是关于如何在 Python 中将“托管缓冲区”转换为 Callable的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Python(不使用 MedPy)或 C 中将 *.mha 文件转换为 *.nii 文件?

如何在 Python 中将单个字符转换为其十六进制 ASCII 值?

如何在python中将单个字符转换为十六进制ascii值

如何在python中将数据框转换为数组? [复制]

如何在python中将数组字符串转换为数组[重复]

如何在Python中将字符串列表转换为字典列表[重复]