3

这是我无法使用文档回答的 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 类的 to-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_converter,假设 A 已经使用 class_<> 导出?这将彻底解决我的问题(尽管我愿意接受其他建议)。

谢谢!ķ

4

1 回答 1

3

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

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

因此,一种解决方案是禁止使用 类的默认转换器Aboost::noncopyable然后注册一个自定义转换器,该转换器将创建 Python 对象,该对象A包含B. 这种方法将在 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))
于 2013-07-24T18:33:35.450 回答