3

我有混合系统(c++,boost python)。在我的 C++ 代码中有非常简单的层次结构

class Base{...}
class A : public Base{...}
class B : public Base{...}

还有 2 个业务(在 C++ 上)方法

smart_ptr<Base> factory() //this produce instances of A and B
void consumer(smart_ptr<A>& a) //this consumes instance of A

在 python 代码中,我创建 A 的实例factory并尝试调用使用者方法:

v = factory() #I'm pretty sure that it is A instance
consumer(v)

绝对合理我有例外:

consumer(Base) 中的 Python 参数类型与 C++ 签名不匹配:consumer(class A{lvalue})

发生这种情况是因为无法告诉 Boost 应该进行一些转换工作。

有什么方法可以指定动态转换行为吗?先感谢您。

4

3 回答 3

4

说到boost::shared_ptr,Boost.Python 通常会提供所需的功能。to_python在这种特殊情况下,只要模块声明定义了Base由 持有的boost::shared_ptr<Base>,并且 Boost.Python 被告知A继承自,就不需要显式提供自定义转换器Base

BOOST_PYTHON_MODULE(example) {
  using namespace boost::python;
  class_<Base, boost::shared_ptr<Base>, 
         boost::noncopyable>("Base", no_init);
  class_<A, bases<Base>,
         boost::noncopyable>("A", no_init);
  def("factory",  &factory);
  def("consumer", &consumer);
}

Boost.Python 目前不支持自定义左值转换器,因为它需要更改核心库。因此,该consumer函数需要接受boost:shared_ptr<A>按值或按常量引用。以下任何一个签名都应该有效:

void consumer(boost::shared_ptr<A> a)
void consumer(const boost::shared_ptr<A>& a)

这是一个完整的例子:

#include <boost/python.hpp>
#include <boost/make_shared.hpp>

class Base
{
public:
  virtual ~Base() {}
};

class A
  : public Base
{
public:
  A(int value) : value_(value) {}
  int value() { return value_; };
private:
  int value_;
};

boost::shared_ptr<Base> factory()
{
  return boost::make_shared<A>(42);
}

void consumer(const boost::shared_ptr<A>& a)
{
  std::cout << "The value of object is " << a->value() << std::endl;
}

BOOST_PYTHON_MODULE(example) {
  using namespace boost::python;
  class_<Base, boost::shared_ptr<Base>, 
         boost::noncopyable>("Base", no_init);
  class_<A, bases<Base>,
         boost::noncopyable>("A", no_init);
  def("factory",  &factory);
  def("consumer", &consumer);
}

以及用法:

>>> from example import *
>>> x = factory()
>>> type(x)
<class 'example.A'>
>>> consumer(x)
The value of object is 42
>>> 

由于指定的模块声明Base是 的基类A,Boost.Python 能够解析从factory()to返回的类型example.A

于 2013-02-08T18:56:13.380 回答
3

就在这里。您必须声明自己的“from-python”转换器。boost.python 文档中对它进行了模糊的解释(请查看常见问题解答中的此答案),但您会在网上找到教程,例如这个。这是一个完整的示例,基于您想要执行的初始输入:

#include <boost/python.hpp>
#include <boost/make_shared.hpp>

class Base {
  public:
    virtual ~Base() {}
};

class A: public Base {
  public:
    A(int v): _value(v) {}
    int _value;
};

boost::shared_ptr<Base> factory() {
  return boost::make_shared<A>(27);
}

void consumer(boost::shared_ptr<A> a) {
  std::cout << "The value of object is " << a->_value << std::endl;
}

struct a_from_base {

  typedef typename boost::shared_ptr<A> container_type;

  // Registers the converter for boost.python
  a_from_base() {
    boost::python::converter::registry::push_back(&convertible, &construct,
        boost::python::type_id<container_type>());
  }

  // Determines convertibility: checks if a random 
  // object is convertible to boost::shared_ptr<A>
  static void* convertible(PyObject* ptr) {
    boost::python::object object(boost::python::handle<>(boost::python::borrowed(ptr)));
    boost::python::extract<boost::shared_ptr<Base> > checker(object);
    if (checker.check()) { //is Base
      boost::shared_ptr<Base> base = checker();
      if (boost::dynamic_pointer_cast<A>(base)) return ptr; //is A
    }
    return 0; //is not A
  }

  // Runs the conversion (here we know the input object *is* convertible)
  static void construct(PyObject* ptr, boost::python::converter::rvalue_from_python_stage1_data* data) {

    // This is some memory allocation black-magic that is necessary for bp
    void* storage = ((boost::python::converter::rvalue_from_python_storage<container_type>*)data)->storage.bytes;
    new (storage) container_type();
    data->convertible = storage;
    container_type& result = *((container_type*)storage); //< your object

    // The same as above, but this time we set the provided memory
    boost::python::object object(boost::python::handle<>(boost::python::borrowed(ptr)));
    boost::shared_ptr<Base> base = boost::python::extract<boost::shared_ptr<Base> >(object);
    result = boost::dynamic_pointer_cast<A>(base);
  }

};

// Your boost python module: compile it and run your test
// You should get "The value of object is 27".
BOOST_PYTHON_MODULE(convertible) {
  using namespace boost::python;
  class_<Base, boost::shared_ptr<Base>, boost::noncopyable>("Base", no_init);
  class_<A, boost::shared_ptr<A>, bases<Base>, boost::noncopyable>("A", no_init);
  a_from_base();
  def("factory", &factory);
  def("consumer", &consumer);
}

您可以通过为您的类编写另一个 from-python 转换器来扩展此示例,B或者只是模板上面的结构以容纳Base.

于 2013-02-08T13:47:57.483 回答
3

正如它所提出的那样,您的问题无法使用自动(或手动)from-python 转换器来解决,如该线程中所述。您将需要对其进行修补,模拟动态类型行为,以便它按照您对 Python 的期望工作。我假设,由于您传递的是非常量引用,因此您需要在consumer()方法内更改它。

这是一个完整的示例。C++ 管道需要知道所有可能的派生Base或 aTypeError在 Python 中提出。动手:

#include <boost/python.hpp>
#include <boost/make_shared.hpp>

class Base {
  public:
    Base(int value) : value_(value) {}
    virtual ~Base() {}
    int value() const { return value_; };
    void value(int value) { value_ = value; }
  private:
    int value_;
};

class A : public Base {
  public:
    A(int value): Base(value) {}
};

class B : public Base {
  public:
    B(int value): Base(value) {}
};

class C : public Base {
  public:
    C(int value): Base(value) {}
};

boost::shared_ptr<Base> factory(boost::python::str choose) {
  if (choose == "a") return boost::make_shared<A>(1);
  else if (choose == "b") return boost::make_shared<B>(10);
  else return boost::make_shared<C>(100);
}

void consumer_a(boost::shared_ptr<A>& a) {
  std::cout << "The value of object was " << a->value() << std::endl;
  a = boost::make_shared<A>(a->value()+1);
  std::cout << "The new value of object is " << a->value() << std::endl;
}

void consumer_b(boost::shared_ptr<B>& b) {
  std::cout << "The value of object is " << b->value() << std::endl;
  b = boost::make_shared<B>(b->value()+1);
  std::cout << "The new value of object is " << b->value() << std::endl;
}

void consumer_python(boost::shared_ptr<Base>& base) {
  //try the first one
  boost::shared_ptr<A> a = boost::dynamic_pointer_cast<A>(base);
  if (a) {
    consumer_a(a);
    base = a; ///< don't forget the assignment here
    return;
  }

  //try the second one
  boost::shared_ptr<B> b = boost::dynamic_pointer_cast<B>(base);
  if (b) {
    consumer_b(b);
    base = b; ///< don't forget the assignment here
    return;
  }

  //we leave C uncovered to see what happens ;-)

  //if you get here, you can raise an exception for python
  PyErr_Format(PyExc_TypeError, "Input type is neither A or B");
  throw boost::python::error_already_set();
}

//notice you don't even need to declare a binding for A, B or C
BOOST_PYTHON_MODULE(example) {
  using namespace boost::python;
  class_<Base, boost::shared_ptr<Base>, boost::noncopyable>("Base", no_init);
  def("factory",  &factory);
  def("consumer", &consumer_python);
}

编译后,您可以运行此脚本并查看它是否正常工作:

import example
print "Creating object of type A..."
a = example.factory("a")
print "Consuming A twice..."
example.consumer(a)
example.consumer(a)

print "Creating object of type B..."
b = example.factory("b")
print "Consuming B twice..."
example.consumer(b)
example.consumer(b)

print "Creating object of type C..."
c = example.factory("c")
print "Trying to consume (uncovered) C..."
example.consumer(c)

输出应该是这样的:

Creating object of type A...
Consuming A twice...
The value of object was 1
The new value of object is 2
The value of object was 2
The new value of object is 3
Creating object of type B...
Consuming B twice...
The value of object is 10
The new value of object is 11
The value of object is 11
The new value of object is 12
Creating object of type C...
Trying to consume (uncovered) C...
Traceback (most recent call last):
  File "test.py", line 25, in <module>
    example.consumer(c)
TypeError: Input type is neither A or B
于 2013-02-09T05:44:31.273 回答