我有一个接受回调的 C++ 函数,如下所示:
void func(std::function<void(A, B)> callback) { ... }
Run Code Online (Sandbox Code Playgroud)
我想通过给它一个闭包来从 Cython 调用这个函数,即如果我从 C++ 调用它,我会用 lambda 做的事情。如果这是一个 C 函数,它会有一些额外的void*参数:
typedef void(*callback_t)(int, int, void*);
void func(callback_t callback, void *user_data) {
callback(1, 2, user_data);
}
Run Code Online (Sandbox Code Playgroud)
然后我只想通过PyObject*的user_data(有更详细的在这里例子)。
有没有办法以 C++ 的方式做更多的事情,而不必求助于显式user_data?
我相信您的目标是将可调用的 Python 对象传递给接受std::function. 您需要创建一些 C++ 代码来实现它,但它相当简单。
首先尽可能简单地定义“accepts_std_function.hpp”以提供一个说明性示例:
#include <functional>
#include <string>
inline void call_some_std_func(std::function<void(int,const std::string&)> callback) {
callback(5,std::string("hello"));
}
Run Code Online (Sandbox Code Playgroud)
然后诀窍是创建一个包含 aPyObject*和定义的包装类operator()。定义operator()允许将其转换为std::function. 大部分课程只是引用计数。“py_obj_wrapper.hpp”:
#include <Python.h>
#include <string>
#include "call_obj.h" // cython helper file
class PyObjWrapper {
public:
// constructors and destructors mostly do reference counting
PyObjWrapper(PyObject* o): held(o) {
Py_XINCREF(o);
}
PyObjWrapper(const PyObjWrapper& rhs): PyObjWrapper(rhs.held) { // C++11 onwards only
}
PyObjWrapper(PyObjWrapper&& rhs): held(rhs.held) {
rhs.held = 0;
}
// need no-arg constructor to stack allocate in Cython
PyObjWrapper(): PyObjWrapper(nullptr) {
}
~PyObjWrapper() {
Py_XDECREF(held);
}
PyObjWrapper& operator=(const PyObjWrapper& rhs) {
PyObjWrapper tmp = rhs;
return (*this = std::move(tmp));
}
PyObjWrapper& operator=(PyObjWrapper&& rhs) {
held = rhs.held;
rhs.held = 0;
return *this;
}
void operator()(int a, const std::string& b) {
if (held) { // nullptr check
call_obj(held,a,b); // note, no way of checking for errors until you return to Python
}
}
private:
PyObject* held;
};
Run Code Online (Sandbox Code Playgroud)
该文件使用一个非常短的 Cython 文件来完成从 C++ 类型到 Python 类型的转换。“call_obj.pyx”:
from libcpp.string cimport string
cdef public void call_obj(obj, int a, const string& b):
obj(a,b)
Run Code Online (Sandbox Code Playgroud)
然后您只需要创建包装这些类型的 Cython 代码。编译此模块并调用test_func以运行它。("simple_version.pyx":)
cdef extern from "py_obj_wrapper.hpp":
cdef cppclass PyObjWrapper:
PyObjWrapper()
PyObjWrapper(object) # define a constructor that takes a Python object
# note - doesn't match c++ signature - that's fine!
cdef extern from "accepts_std_func.hpp":
void call_some_std_func(PyObjWrapper) except +
# here I lie about the signature
# because C++ does an automatic conversion to function pointer
# for classes that define operator(), but Cython doesn't know that
def example(a,b):
print(a,b)
def test_call():
cdef PyObjWrapper f = PyObjWrapper(example)
call_some_std_func(f)
Run Code Online (Sandbox Code Playgroud)
上面的版本可以工作,但有些限制,因为如果你想用不同的std::function专业化来做到这一点,你需要重写其中的一些(从 C++ 到 Python 类型的转换自然不适合模板实现)。解决此问题的一种简单方法是使用 Boost Python 库object类,它具有模板化的operator(). 这是以引入额外的库依赖为代价的。
首先定义头文件“boost_wrapper.hpp”以简化从PyObject*到的转换boost::python::object
#include <boost/python/object.hpp>
inline boost::python::object get_as_bpo(PyObject* o) {
return boost::python::object(boost::python::handle<>(boost::python::borrowed(o)));
}
Run Code Online (Sandbox Code Playgroud)
然后你只需要 Cython 代码来包装这个类(“boost_version.pyx”)。再次调用test_func
cdef extern from "boost_wrapper.hpp":
cdef cppclass bpo "boost::python::object":
# manually set name (it'll conflict with "object" otherwise
bpo()
bpo get_as_bpo(object)
cdef extern from "accepts_std_func.hpp":
void call_some_std_func(bpo) except + # again, lie about signature
def example(a,b):
print(a,b)
def test_call():
cdef bpo f = get_as_bpo(example)
call_some_std_func(f)
Run Code Online (Sandbox Code Playgroud)
一个“setup.py”
from distutils.core import setup, Extension
from Cython.Build import cythonize
extensions = [
Extension(
"simple_version", # the extension name
sources=["simple_version.pyx", "call_obj.pyx" ],
language="c++", # generate and compile C++ code
),
Extension(
"boost_version", # the extension name
sources=["boost_version.pyx"],
libraries=['boost_python'],
language="c++", # generate and compile C++ code
)
]
setup(ext_modules = cythonize(extensions))
Run Code Online (Sandbox Code Playgroud)
(最后一个选项是使用ctypes从 Python 可调用对象生成 C 函数指针。请参阅使用函数指针指向没有 gil 的类的方法(答案的下半部分)和http://osdir.com/ml/python-cython- devel/2009-10/msg00202.html。我不打算在这里详细介绍。)
| 归档时间: |
|
| 查看次数: |
1519 次 |
| 最近记录: |