Boost Python可迭代可转换掩码非可迭代构造函数

时间:2014-01-04 20:52:12

标签: boost-python

我基于对此question的回答实现了一个boost python转换器,以自动从Python可迭代转换为C ++向量。而且,通过实施,我的意思是逐字复制代码。由于@Tanner Sansbury,转换器就像一个魅力。

我使用转换器接受构造函数的向量参数。但是,当加载转换器时,当我调用非可迭代的int构造函数时,我得到以下错误:

>>> a = term.Term([3])
>>> b = term.Term(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not utterable

尽管两个构造函数都在boost python模块中声明,如下所示:

bp::class_<expr::Term>("Term", bp::init<int>())
    .def(bp::init<std::vector<int> const&>())

如果未加载转换器,则int构造函数可以正常工作,并且在尝试按预期使用向量构造函数时出错:

>>> a = term.Term(3)
>>> b = term.Term([3]);
    ArgumentError: Python argument types in
        Term.__init__(Term, list)
    did not match C++ signature:
        __init__(_object*, std::vector<int, std::allocator<int> >)
        __init__(_object*, int)

这是一个功能,错误还是我做错了什么?

1 个答案:

答案 0 :(得分:3)

对我来说这看起来像个错误。据我所知,调度没有正确处理可转换支票的退货。另外,我通过更改构造函数的注册顺序(通过boost::python::initboost::python::make_constructor())观察到不同的结果。

例如,如果设置类似于问题中描述的问题,则以下结果会产生类似结果:

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // ...

  python::class_<spam>("Spam", python::no_init)
    .def(python::init<int>())
    .def(python::init<std::vector<int> >())
    ;
}

Interactive Python:

>>> import example
>>> spam = example.Spam(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
>>> spam = example.Spam([42])

更改init订单后:

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // ...

  python::class_<spam>("Spam", python::no_init)
    .def(python::init<std::vector<int> >())
    .def(python::init<int>())
    ;
}

相同的Python代码有效:

>>> import example
>>> spam = example.Spam(42)
>>> spam = example.Spam([42])

鉴于这种未指定的行为,如果构造函数不是功能不同并且仅作为方便提供或者是Pythonic,那么可能值得在Python中修补类构造函数以始终使用列表构造函数。例如,C ++扩展example模块将重命名为_exampleexample.py将执行必要的修补。这是一个完整的例子:

#include <vector>
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>

/// @brief Type that allows for registration of conversions from
///        python iterable types.
struct iterable_converter
{
  /// @note Registers converter from a python interable type to the
  ///       provided type.
  template <typename Container>
  iterable_converter&
  from_python()
  {
    boost::python::converter::registry::push_back(
      &iterable_converter::convertible,
      &iterable_converter::construct<Container>,
      boost::python::type_id<Container>());
    return *this;
  }

  /// @brief Check if PyObject is iterable.
  static void* convertible(PyObject* object)
  {
    return PyObject_GetIter(object) ? object : NULL;
  }

  /// @brief Convert iterable PyObject to C++ container type.
  ///
  /// Container Concept requirements:
  ///
  ///   * Container::value_type is CopyConstructable.
  ///   * Container can be constructed and populated with two iterators.
  ///     I.e. Container(begin, end)
  template <typename Container>
  static void construct(
    PyObject* object,
    boost::python::converter::rvalue_from_python_stage1_data* data)
  {
    namespace python = boost::python;
    // Object is a borrowed reference, so create a handle indicting it is
    // borrowed for proper reference counting.
    python::handle<> handle(python::borrowed(object));

    // Obtain a handle to the memory block that the converter has allocated
    // for the C++ type.
    typedef python::converter::rvalue_from_python_storage<Container>
                                                                 storage_type;
    void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;

    typedef python::stl_input_iterator<typename Container::value_type>
                                                                     iterator;

    // Allocate the C++ type into the converter's memory block, and assign
    // its handle to the converter's convertible variable.  The C++
    // container is populated by passing the begin and end iterators of
    // the python object to the container's constructor.
    data->convertible = new (storage) Container(
      iterator(python::object(handle)), // begin
      iterator());                      // end
  }
};

/// @brief Mockup class.
struct spam
{
  explicit spam(const std::vector<int>&) {}
};

BOOST_PYTHON_MODULE(_example)
{
  namespace python = boost::python;

  // Register interable conversions.
  iterable_converter()
    .from_python<std::vector<int> >()
    ;

  // Expose Spam with a single constructor.  It will be adapted.
  python::class_<spam>("Spam", python::init<std::vector<int> >());
}

example.py将修补_example.Spam.__init__

from _example import *

def _patched_spam_init():
    ''' Monkey-patch Spam.__init__ to force the first argument to be
        iterable.

    '''
    # Get handle to delegate.
    _Spam_init = Spam.__init__

    # Patched method that delegates to the original.
    def patch(self, value):
        try:
            (x for x in value)
        except TypeError:
            value = [value]
        return _Spam_init(self, value)

    # Set __init__ to the patched method.
    Spam.__init__ = patch

_patched_spam_init()

修补对最终用户透明。相同的Interactive Python代码可以工作:

>>> import example
>>> spam = example.Spam(42)
>>> spam = example.Spam([42])

如果构造函数的功能不同,那么修补可能仍然有用。可以通过make_constructor()使用唯一标记类型公开辅助函数,以强制在Boost.Python中进行适当的调度。然后,扩展类可以在Python中进行猴子修补以检查参数并通过标记类型调用适当的类构造函数。由于标记类型仅在C ++扩展和修补Python模块之间内部使用,因此调度对最终用户是透明的。