需要提升 python 显式类型转换

Posted

技术标签:

【中文标题】需要提升 python 显式类型转换【英文标题】:boost python explicit typecast needed 【发布时间】:2013-02-01 17:49:41 【问题描述】:

我有混合系统(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 代码中,我使用 factory 创建 A 的实例并尝试调用使用者方法:

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

绝对合理,我有例外:

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

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

有什么方法可以指定动态转换行为吗? 提前谢谢你。

【问题讨论】:

您好,smart_ptr(consumer() 所期望的)不是 smart_ptr 的派生类,因此 dynamic_cast 是不够的,或者您需要 dynamic_pointer_cast.. . @GHL 大多数智能指针声明都有很多提供所需语义的操作,例如在 boost 中:template&lt;class T, class U&gt; shared_ptr&lt;T&gt; dynamic_pointer_cast(shared_ptr&lt;U&gt; const &amp; r)template&lt;class T&gt; ... template&lt;class Y&gt; explicit shared_ptr(Y * p)。所以我提到的“动态”更广泛,然后引用 dynamic_cast 运算符 【参考方案1】:

对于boost::shared_ptr,Boost.Python 通常提供所需的功能。在这种特殊情况下,只要模块声明定义 Baseboost::shared_ptr&lt;Base&gt; 持有,并且 Boost.Python 被告知 A 继承自 Base,就无需显式提供自定义 to_python 转换器.

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 函数需要通过值或 const-reference 接受 boost:shared_ptr&lt;A&gt;。以下任何一个签名都应该有效:

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
>>> 

由于模块声明指定BaseA 的基类,Boost.Python 能够将factory() 返回的类型解析为example.A

【讨论】:

【参考方案2】:

是的,有。您必须声明自己的“from-python”转换器。 boost.python 文档中对它进行了模糊的解释(查看this answer in the FAQ),但您会在网上找到教程,例如this one。这是一个完整的示例,基于您想要做的初始输入:

#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);

您可以通过为您的类 B 编写另一个 from-python 转换器来扩展此示例,或者,只需模板上面的结构以适应 Base 的所有子级。

【讨论】:

以纯粹的形式展示您的作品!但是我在我的示例中发现了一个严重的错误 - 只是让消费者而不是 void consumer(smart_ptr&lt;A&gt; a)void consumer(smart_ptr&lt;A&gt;&amp; a) - 一个&符号会破坏一切。任何的想法? (实际上我正在编辑我的示例) 当然,问题在于 from-python 转换器只能对 const T& 或 T 进行操作。Boost.Python 中目前无法处理非 const 引用,如 this thread 中所述。因此,在这种情况下,您唯一的选择是手动编写转换器。我将发布另一个答案。 另请注意,twsansbury 的答案比我的更优雅。我忘记了 boost::shared_ptr&lt;&gt; 已经涵盖了所有内容 - 不需要编写专门的 from-python 转换器。 仅供参考,实际上这个例子即使没有转换器也能很好地工作。 (我会在你的下一个答案上发表独立评论)。 这就是我在之前的评论中所说的,指的是 twsansbury 的答案——尽管对于非常量引用,它不会像你期望的那样工作。【参考方案3】:

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

这是一个完整的示例。 C++ 管道需要知道BaseTypeError 的所有可能派生是在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

【讨论】:

解决方法的好例子,谢谢,无论如何 +1 每个答案。不幸的是,这不适用于我——因为 boost-to-python 声明是为我的代码自动生成的,我不能随意更改声明。这就是为什么像转换器这样的方式更适用的原因,在此之后我需要在 c++ 上创建适配器,它最终是不需要 & 符号的所需方法的外观

以上是关于需要提升 python 显式类型转换的主要内容,如果未能解决你的问题,请参考以下文章

145-显式转换和隐式转换

Python 强制类型转换

C# 类型转换

Java基础数据类型转换方法入门

转换为枚举类型需要显式转换(static_cast、C 样式转换或函数样式转换)

在 C# 中引用类型的内存方面的显式转换解释