2016-08-19 4 views
3

C++ У меня есть функция C++, который принимает обратный вызов, как это:Pass замыкание от Cython к

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

Я хочу, чтобы вызвать эту функцию из Cython, придав ему замыкание, то есть то, что я сделал бы с лямбдой, если я вызываю его из C++. Если бы это была функция C, она будет иметь некоторые дополнительные void* аргументы:

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

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

, и тогда я бы просто передать PyObject* как user_data (есть более подробная example here).

Есть ли способ сделать этот путь более в C++, без необходимости прибегать к явной user_data?

+1

Googling 'cython std function' дает [пример] (https://github.com/hildensia/py_c_py) (а не библиотеку), которая кажется актуальной. – user2357112

+0

@bereal Сделайте свой вопрос самостоятельно пожалуйста (относительно примеров), так как это не очень полезно для будущих исследований. –

+0

@ πάνταῥεῖ fair enoug h, будет обновляться, как только я вернусь к клавиатуре. – bereal

ответ

3

То, что я считаю, вы стремитесь сделать, это передать вызываемый объект 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")); 
} 

Хитрость затем создать класс-оболочку, которая содержит PyObject* и определяет 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; 
}; 

Этот файл использует очень короткий файл 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) 

Вы тогда просто нужно создать код 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) 

Вышеприведенная версия работает, но несколько ограничен в том, что если вы хотите сделать это с другой std::function специализации вам нужно переписать некоторые из них (и преобразования от типов C++ до Python, естественно, не поддается реализации шаблона). Один простой способ обойти это использовать подталкивание Python библиотеки object класс, который имеет шаблонный operator(). Это происходит за счет введения дополнительной зависимости от библиотеки.

Первое определение заголовка "boost_wrapper.hpp" для упрощения 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"). Опять же, называть 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».ру»

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 для создания функции C указатель из Python отзывной См. Using function pointers to methods of classes without the gil (нижняя половина ответа) и http://osdir.com/ml/python-cython-devel/2009-10/msg00202.html. Я не буду вдаваться в подробно об этом здесь.)

+0

Просто отлично, особенно спасибо за повышение templated 'operator()'. Я на самом деле уже что-то сделал в соответствии с тем, о чем говорил @ user2357112, но переписывать обертки для каждой новой сигнатуры обратного вызова было очень раздражающе. В этом примере также отсутствовала часть подсчета ссылок. Большое спасибо! – bereal

+0

Boost - это, очевидно, путь сюда (это почти без усилий!). Я хотел показать, как вы можете сделать это без повышения, но я бы не использовал этот код, если только boost действительно не является вариантом. – DavidW