Boost.Python and Boost.Function

Boost.Python and Boost.Function

本文关键字:Boost Function Python and      更新时间:2023-10-16

我想包装一个boost::function类成员,以便它可以以以下方式使用:

using namespace boost;
using namespace boost::python;
struct gui_button_t
{
    function<void()> on_pressed;
};
class_<gui_button_t>("GuiButton", init<>())
    .def("on_pressed", &gui_button_t::on_pressed);

在Python中:

def callback_function():
    print 'button has been pressed'
button = GuiButton()
button.on_pressed = callback_function
button.on_pressed() # function should be callable from C++ or Python

但是,尝试这种方法会产生大量关于类模板参数等的错误。

我已经做了一些搜索,但还没能找到我一直在寻找的答案。下面的文章有点接近,但它们没有直接触及这个主题。

http://bfroehle.com/2011/07/18/boost-python-and-boost-function-ii/

我在这里做错了什么?我需要做些什么才能获得此功能所需的接口?

提前感谢。

Python只接受指向函数和成员函数的指针。我们需要做的是将可调用对象转换为函数指针。这里的关键思想是

  1. 没有捕获的lambda可以转换为函数指针(通过巫术)
  2. 函数指针的解释方式与Python中的成员函数相同:第一个参数是self

在你的例子中,我们需要做的是生成这个lambda:

+[](gui_button_t* self) {
    self->on_pressed();
}

您已经可以在Boost中按原样使用它。Python,因为这是一个完全正常的函数指针。然而,我们想要一个对任何可调用成员都有效的解决方案。当可以支持任何东西时,为什么只支持boost::function ?

我们将从@Columbo的closure_traits开始,但另外添加一种提取参数列表的方法;

template <typename...> struct typelist { };
template <typename C, typename R, typename... Args>                        
struct closure_traits<R (C::*) (Args... REM_CTOR var) cv>                  
{                                                                          
    using arity = std::integral_constant<std::size_t, sizeof...(Args) >;   
    using is_variadic = std::integral_constant<bool, is_var>;              
    using is_const    = std::is_const<int cv>;                             
                                                                           
    using result_type = R;                                                 
                                                                           
    template <std::size_t i>                                               
    using arg = typename std::tuple_element<i, std::tuple<Args...>>::type; 
                                                                           
    using args = typelist<Args...>;                                        
};

然后为任何可调用成员编写包装器。由于lambda不能进行任何捕获,因此使用将可调用对象作为模板参数:

template <typename CLS, typename F, F CLS::*callable>
class wrap { ... };

我将使用c++ 14的auto返回类型扣除来节省一些类型。我们创建了一个顶级make_pointer()静态成员函数,它只转发给另外接受参数的助手成员函数。完整的wrap如下所示:

template <typename CLS, typename F, F CLS::*callable>
class wrap {
public:
    static auto make_pointer() {
        return make_pointer_impl(typename closure_traits<F>::args{});
    }
private:
    template <typename... Args>
    static auto make_pointer_impl(typelist<Args...> ) {
        // here is our lambda that takes the CLS as the first argument
        // and then the rest of the callable's arguments,
        // and just calls it
        return +[](CLS* self, Args... args) {
            return (self->*callable)(args...);
        };
    }
};

我们可以用它来包装你的按钮:

void (*f)(gui_button_t*) = wrap<gui_button_t, 
                                decltype(gui_button_t::on_pressed),
                                &gui_button_t::on_pressed
                                >::make_pointer();

这有点啰嗦和重复,所以让我们创建一个宏(叹气):

#define WRAP_MEM(CLS, MEM) wrap<CLS, decltype(CLS::MEM), &CLS::MEM>::make_pointer()

我们得到:

void (*f)(gui_button_t*) = WRAP_MEM(gui_button_t, on_pressed);
f(some_button); // calls some_button->on_pressed()

因为这给了我们一个指向函数的指针,所以我们可以直接在普通Boost中使用它。Python API:

class_<gui_button_t>("GuiButton", init<>())
    .def("on_pressed", WRAP_MEM(gui_button_t, on_pressed));

Demo演示了指向std::function成员的函数指针和指向operator()成员的struct函数指针。


上面的

使您能够暴露一个可调用对象。如果你还想做赋值操作,例如:

button = GuiButton()
button.on_pressed = callback_function
button.on_pressed()

我们需要做点别的。在Python中,您无法以有意义的方式公开operator=,因此为了支持上述功能,您必须重写__setattr__。现在,如果你愿意:

button.set_on_pressed(callback_function)

我们可以扩展上面的wrap解决方案来添加setter,其实现将是,在上面的思路中:

static auto set_callable() {
    return make_setter_impl(
        typelist<typename closure_traits<F>::result_type>{},
        typename closure_traits<F>::args{});
}
template <typename R, typename... Args>
static auto make_setter_impl(typelist<R>, typelist<Args...> ) {
    return +[](CLS* self, py::object cb) {
        (self->*callable) = [cb](Args... args) {
            return py::extract<R>(
                cb(args...))();
        };
    };
}
// need a separate overload just for void
template <typename... Args>
static auto make_setter_impl(typelist<void>, typelist<Args...> ) {
    return +[](CLS* self, py::object cb) {
        (self->*callable) = [cb](Args... args) {
            cb(args...);
        };
    };
}
#define SET_MEM(CLS, MEM) wrap<CLS, decltype(CLS::MEM), &CLS::MEM>::set_callable()

你可以通过:

.def("set_on_pressed", SET_MEM(button, on_pressed))
然而,如果你坚持支持直接赋值,那么你就需要额外公开如下内容:
static void setattr(py::object obj, std::string attr, py::object val)
{
     if (attr == "on_pressed") {
         button& b = py::extract<button&>(obj);
         SET_MEM(button, on_pressed)(&b, val);
     }
     else {
         py::str attr_str(attr);
         if (PyObject_GenericSetAttr(obj.ptr(), attr_str.ptr(), val.ptr()) {
             py::throw_error_already_set();
         }
     }
}

.def("__setattr__", &button::setattr);

这是可行的,但是您必须为每个想要设置的函子添加更多的情况。如果每个类只有一个类似函子的对象,这可能不是什么大问题,甚至可以编写一个更高阶的函数来为给定的属性名生成一个特定的类似setattr的函数。但如果你有倍数,它会逐渐变得比简单的set_on_pressed解决方案更糟糕。


如果c++ 14不可用,我们只能显式指定make_pointer的返回类型。我们需要一些方便的类型特征。concat:

template <typename T1, typename T2>
struct concat;
template <typename T1, typename T2>
using concat_t = typename concat<T1, T2>::type;
template <typename... A1, typename... A2>
struct concat<typelist<A1...>, typelist<A2...>> {
    using type = typelist<A1..., A2...>;
};

然后将返回类型和typelist转换为函数指针:

template <typename R, typename T>
struct make_fn_ptr;
template <typename R, typename... Args>
struct make_fn_ptr<R, typelist<Args...>> {
    using type = R(*)(Args...);
};
template <typename R, typename T>
using make_fn_ptr_t = typename make_fn_ptr<R, T>::type;

然后在wrap中,我们可以将结果类型定义为:

using R = make_fn_ptr_t<
                typename closure_traits<F>::result_type,
                concat_t<
                    typelist<CLS*>,
                    typename closure_traits<F>::args
                    >
                >;

,用它代替auto。<<strong> c++ 11演示/strong>。