То, что я считаю, вы стремитесь сделать, это передать вызываемый объект 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. Я не буду вдаваться в подробно об этом здесь.)
Googling 'cython std function' дает [пример] (https://github.com/hildensia/py_c_py) (а не библиотеку), которая кажется актуальной. – user2357112
@bereal Сделайте свой вопрос самостоятельно пожалуйста (относительно примеров), так как это не очень полезно для будущих исследований. –
@ πάνταῥεῖ fair enoug h, будет обновляться, как только я вернусь к клавиатуре. – bereal