将Cython的闭包传递给C ++ [英] Pass a closure from Cython to C++

查看:78
本文介绍了将Cython的闭包传递给C ++的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个接受回调的C ++函数,如下所示:

I have a C++ function that accepts a callback, like this:

void func(std::function<void(A, B)> callback) { ... }

我想从Cython调用此函数通过给它一个闭包,也就是说,如果我从C ++调用它,我会用lambda来完成的。如果这是C函数,它将有一些额外的 void * 参数:

I want to call this function from Cython by giving it a closure, i.e. something I would have done with a lambda if I was calling it from C++. If this was a C function, it would have some extra void* arguments:

typedef void(*callback_t)(int, int, void*);

void func(callback_t callback, void *user_data) {
    callback(1, 2, user_data);
}

然后我将通过 PyObject * 作为 user_data (有更详细的此处的示例)。

and then I would just pass PyObject* as user_data (there is a more detailed example here).

有没有办法以C ++的方式进行更多操作,而不必求助于显式的 user_data

Is there way to do this more in C++ way, without having to resort to explicit user_data?

推荐答案

我相信您打算做的是传递可调用对象Python反对接受 std :: function 的对象。您需要创建一些C ++代码以使其实现,但它相当简单。

What I believe you're aiming to do is pass a callable Python object to something accepting a std::function. You need to do create a bit of C++ code to make it happen, but it's reasonably straightforward.

从尽可能简单地定义 accepts_std_function.hpp开始,以提供一个说明性示例:

Starting by defining "accepts_std_function.hpp" as simply as possible to provide an illustrative example:

#include <functional>
#include <string>

inline void call_some_std_func(std::function<void(int,const std::string&)> callback) {
    callback(5,std::string("hello"));
}

然后,诀窍是创建一个包含<$ c $的包装类c> PyObject * 并定义 operator()。定义 operator()可以将其转换为 std :: function 。大部分班级只是口算。 py_obj_wrapper.hpp:

The trick is then to create a wrapper class that holds a PyObject* and defines operator(). Defining operator() allows it to be converted to a std::function. Most of the class is just refcounting. "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;
};

此文件使用非常短的Cython文件来完成从C ++类型到Python类型的转换。 call_obj.pyx:

This file uses a very short Cython file to do the conversions from C++ types to Python types. "call_obj.pyx":

from libcpp.string cimport string

cdef public void call_obj(obj, int a, const string& b):
    obj(a,b)

您然后只需要创建Cython代码即可包装这些类型。编译该模块并调用 test_func 来运行它。 ( simple_version.pyx:)

You then just need to create the Cython code wraps these types. Compile this module and call test_func to run this. ("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)






上述版本适用,但是如果您要使用其他 std :: function 专业化需要重写其中的一些专业化(从C ++到Python类型的转换并不自然地适合于模板实现)。一种简单的解决方法是使用Boost Python库 object 类,该类具有模板化的 operator()


The above version works but is somewhat limited in that if you want to do this with a different std::function specialization you need to rewrite some of it (and the conversion from C++ to Python types doesn't naturally lend itself to a template implementation). One easy way round this is to use the Boost Python library object class, which has a templated operator(). This comes at the cost of introducing an extra library dependency.

首先定义标头 boost_wrapper.hpp以简化 PyObject * boost :: python :: object

First defining the header "boost_wrapper.hpp" to simplify the conversion from PyObject* to 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)));
}

然后,您只需使用Cython代码包装此类( boost_version.pyx )。同样,从 boost_wrapper.hpp中调用 test_func

You then just need to Cython code to wrap this class ("boost_version.pyx"). Again, call 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)






A设置。 py


A "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))






(最后一种选择是使用 ctypes 从Python可调用项生成C函数指针。请参见使用指向类方法的函数指针没有gil (答案的后半部分)和 http://osdir.com/ml/python-cython-devel/2009-10/msg00202.html 。我在这里不做详细介绍。)


(A final option is to use ctypes to generate a C function pointer from a Python callable. See Using function pointers to methods of classes without the gil (bottom half of answer) and http://osdir.com/ml/python-cython-devel/2009-10/msg00202.html. I'm not going to go into detail about this here.)

这篇关于将Cython的闭包传递给C ++的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆