绕过to_python class_<>导出类的注册表?

bypassing to_python registry for class exported with class_<>?

本文关键字:注册表 gt class to lt 绕过 python      更新时间:2023-10-16

这里有一个boost.python问题,我无法使用文档回答。

我有两个C++类A和B,这样B是A的子类,在某些条件下,类型A的对象可以转换为类型B的对象。这两个类都是用class_<>导出的。示意图:

class A 
{ 
public:
    bool is_B() const { ... }    // returns true if conversion to B will succeed
    ...
};
class B : public A {
    B(const A &a) { ... }    // should only be called if a.is_B() == true
    ...
};
class_<A>("A") ... ;
class_<B,bases<A> >("B") ... ;

如果可能的话,我希望类A的到python转换器返回类B的对象;否则它应该返回一个类A的对象。模式:

 struct A_to_python_converter {
     PyObject *convert(const A &a) {
         if (a.is_B())
             // return object of class B
             return boost::python::incref(boost::python::object(B(a)).ptr());
         else
             ...  // return object of class A
     }
 }
 boost::python::to_python_converter<A,A_to_python_converter>();

问题是,我不确定用什么代码来代替上面的"…"。如果我放:

// return object of class A
return boost::python::incref(boost::python::object(a).ptr());

那么我得到了一个无限循环,因为A_to_python_converter::convert()将被递归调用。有没有一种方法可以绕过注册表,将类a的对象转换为(PyObject*),而不需要经过a_to_python_controller,因为a已经用class_<>导出?这将彻底解决我的问题(尽管我对其他建议持开放态度)。

谢谢!K

一个类型只需要注册一个到Python的转换。

当类通过boost::python::class_公开时,会发生类型信息和注册。此外,如果未提供boost::noncopyable,则注册到Python和从Python将按值复制T的转换器。用户可以使用boost::python::to_python_converter注册自己的自定义转换器。

因此,一种解决方案是使用boost::noncopyable抑制类A的默认转换器,然后注册一个自定义转换器,该转换器将创建包含A实例或B实例的Python对象。这种方法将在Boost.Python中使用较低级别的API来处理实例创建。

/// @brief Custom converter that converts A to either an A or B Python object.
struct class_A_cref_wrapper
  : boost::python::to_python_converter<A, class_A_cref_wrapper>
{
  // Type that makes instances that hold A by value.
  typedef boost::python::objects::make_instance<A,
        boost::python::objects::value_holder<A>
  > instance_maker;
  static PyObject* convert(const A& a)
  {
    namespace python = boost::python;
    return a.is_B()
      ? python::incref(python::object(B(a)).ptr()) // Create B.
      : instance_maker::execute(boost::ref(a));    // Create A.      
  }
};

下面是一个完整的例子来演示这种方法:

#include <boost/python.hpp>
// Legacy API.
class A 
{ 
public:
  A() : b_(false)   {}
  A(bool b) : b_(b) {}
  bool is_B() const { return b_; } // true if conversion to B will succeed
private:
  bool b_;
};
class B: public A
{
public:
  B() : A()            {}
  B(const A& a) : A(a) {}
};
/// @brief Factory functions that return an A type with is_B of false.
A make_A() { return A(false); }
/// @brief Factory functions that return an A type with is_B of true.
A make_B() { return A(true);  }
/// @brief Custom converter that converts A to either an A or B Python object.
struct class_A_cref_wrapper
  : boost::python::to_python_converter<A, class_A_cref_wrapper>
{
  // Make and hold instances by value.
  typedef boost::python::objects::make_instance<A,
        boost::python::objects::value_holder<A>
  > instance_maker;
  static PyObject* convert(const A& a)
  {
    namespace python = boost::python;
    return a.is_B()
      ? python::incref(python::object(B(a)).ptr()) // Create B.
      : instance_maker::execute(boost::ref(a));    // Create A.      
  }
};
BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  // Expose A and B classes.  Use boost::noncopyable to suppress to-Python
  // and from-Python converter regristration for class A.
  python::class_<A, boost::noncopyable>("A");
  python::class_<B, python::bases<A> >("B");
  // Register a custom converter for A.
  class_A_cref_wrapper();
  // Expose factory functions that always return an A type.  This will
  // cause to_python converters to be invoked when invoked from Python.
  python::def("make_A", &make_A);
  python::def("make_B", &make_B);
}

交互式使用:

>>> import example
>>> assert(isinstance(example.make_A(), example.A))
>>> assert(isinstance(example.make_B(), example.B))
>>> assert(isinstance(example.make_B(), example.A))
>>> assert(not isinstance(example.make_A(), example.B))