使用boost::python公开具有std::函数作为参数的C++成员函数

Expose C++ member function that has std::function as argument with boost::python

本文关键字:函数 参数 C++ 成员 python boost 使用 std      更新时间:2023-10-16

我有一个类,它包含一个std::函数属性。我使用成员函数设置这个属性的值,所以类看起来像这样:

class ClassName
{    
public:
    void SetCallbackFunction(std::function<void (int i)> callbackFun) {
        m_callbackFunction = callbackFun;
    }
protected:
    std::function<void (int i)> m_callbackFunction;
};

我需要向Python公开这个类,当然,我还需要公开SetCallbackFunction函数。如何使用boost::python完成此操作?

由于Python对象都是可调用的和可复制的,最简单的方法是将一个辅助函数公开为接受boost::python::objectSetCallbackFunction,然后委托给实际的SetCallbackFunction函数:

void ClassName_SetCallbackFunction_aux(ClassName& self, boost::python::object object)
{
  self.SetCallbackFunction(object);
}
BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<ClassName>("ClassName", python::init<>())
    .def("set_callback", &ClassName_SetCallbackFunction_aux)
    // ...
    ;
}

ClassName::SetCallbackFunction直接暴露于Python并被调用时,Boost.Python将在运行时搜索其注册表,以找到std::function<void (int)>的from Python转换器。由于此转换尚未显式注册,Boost.Python将无法调度函数调用。辅助函数避免了这种运行时转换检查,并从boost::python::object构造std::function<void (int)>对象,因为boost::python::object既是可调用的,也是可复制构造的。


下面是一个示例,演示使用辅助函数将Python对象分配为回调:

#include <functional> // std::function
#include <boost/python.hpp>
// Legacy API.
class spam
{
public:
  void SetCallbackFunction(std::function<void (int)> callback)
  {
    callback_ = callback;
  }
  void perform(int x)
  {
    callback_(x);
  }
private:
  std::function<void (int)> callback_;
};
BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  // Expose spam.
  python::class_<spam>("Spam", python::init<>())
    // Use an auxiliary function to set Python callbacks.
    .def("set_callback", +[](spam& self, boost::python::object object) {
      self.SetCallbackFunction(object);
    })
    .def("perform", &spam::perform)
    ;
}

交互式使用:

>>> import example
>>> called = False
>>> def perform_x(x):
...     assert(42 == x)
...     global called
...     called = True
... 
>>> spam = example.Spam()
>>> spam.set_callback(perform_x)
>>> assert(not called)
>>> spam.perform(42)
>>> assert(called) # Verify callback was invoked
>>> spam.set_callback(lambda: None)
>>> try:
...     spam.perform(42)
...     assert(False) # Verify callback fails (the lambda accepts no args)
... except TypeError:
...     pass
...