我基于对此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)
这是一个功能,错误还是我做错了什么?
答案 0 :(得分:3)
对我来说这看起来像个错误。据我所知,调度没有正确处理可转换支票的退货。另外,我通过更改构造函数的注册顺序(通过boost::python::init
或boost::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
模块将重命名为_example
,example.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模块之间内部使用,因此调度对最终用户是透明的。