Boost.Python的自定义转换器
我有一个类服用载体为参数(二进制文件的内容)。
I have a class taking a vector as parameter (a binary file content).
我想蟒蛇STR类型转换为unsigned char型的载体,但只为我的类方法之一。
I would like to convert python 'str' type into vector of unsigned char but only for one of my class method.
BOOST_PYTHON_MODULE(hello) { class_<Hello>("Hello").
// This method takes a string as parameter and print it
.def("printChar", &Hello::printChar)
// This method takes a vector<unsigned char> parameter
.def("storeFile", &Hello::storeFile) }
使用自定义转换似乎正是我需要的,但如果我修改我的boost ::蟒蛇::转换::注册表它会修改我到printChar所有来电,并通过串所有的Python方法为参数将被转换为矢量。
Using custom converter seems to be what I need but if I modify my boost::python::converter::registry it will be modify for all my calls to printChar and all python methods passing string as parameter will be converted to vector.
我如何注册每个方法转换器?
How can I register a per-method converter ?
有两种方法解决这个问题:
There are two approaches to this problem:
- 导出一个辅助功能
Hello.storeFile
接受的boost ::蟒蛇:: STR
,建的std ::矢量&lt; unsigned char型方式&gt;
从字符串,并委托给C ++你好:: storeFile
成员函数 - 编写自定义转换器。虽然转换器不能在每个函数基础上进行注册,他们是相当好范围,以不执行任何意外的转换。这种方法通常会提供更多的可重用性。
- Export a helper function as
Hello.storeFile
that acceptsboost::python::str
, constructsstd::vector<unsigned char>
from the string, and delegates to the C++Hello::storeFile
member function. - Write a custom converter. While converters cannot be registered on a per-function basis, they are fairly well scoped as to not perform any unintended conversions. This approach often provides more reusability.
使用一个辅助功能将不会影响任何其他导出函数。因此,Python字符串之间的转换和的std ::矢量&lt; unsigned char型&GT;
只会发生在 Hello.storeFile
Using a helper function will not affect any other exported function. Thus, the conversion between a python string and std::vector<unsigned char>
will only occur for Hello.storeFile
.
void Hello_storeFile(Hello& self, boost::python::str str)
{
std::cout << "Hello_storeFile" << std::endl;
// Obtain a handle to the string.
const char* begin = PyString_AsString(str.ptr());
// Delegate to Hello::storeFile().
self.storeFile(std::vector<unsigned char>(begin, begin + len(str)));
}
...
BOOST_PYTHON_MODULE(hello)
{
namespace python = boost::python;
python::class_<Hello>("Hello")
// This method takes a string as parameter and print it
.def("printChar", &Hello::printChar)
// This method takes a vector<unsigned char> parameter
.def("storeFile", &Hello_storeFile)
;
}
自定义转换器
A转换器注册有三个部分:
Custom Converter
A converter registration has three parts:
- 来检查,如果一个
的PyObject
是可转换的功能。NULL
的返回表明的PyObject
不能使用已注册的转换器。 - ,从
的PyObject
构建C ++型构造函数。这个功能才会被调用,如果转换器(的PyObject)
不返回NULL
。 - 将要构建的C ++类型。
- A function that checks if a
PyObject
is convertible. A return ofNULL
indicates that thePyObject
cannot use the registered converter. - A construct function that constructs the C++ type from a
PyObject
. This function will only be called ifconverter(PyObject)
does not returnNULL
. - The C++ type that will be constructed.
因此,对于给定的C ++类型,如果转换器(的PyObject)
返回一个非 - NULL
值,则结构(的PyObject)
将创建C ++类型。 C ++类型充当关键到注册表,所以Boost.Python的不应执行意想不到的转换。
Therefore, for a given C++ type, if converter(PyObject)
returns a non-NULL
value, then construct(PyObject)
will create the C++ type. The C++ type serves as a key into the registry, so Boost.Python should not perform unintended conversions.
在这个问题的背景下,我们想为 STD转换器::矢量&lt; unsigned char型&GT;
,其中转换器(的PyObject)
返回非 - NULL
如果的PyObject
是 PyString
和转换器(的PyObject)
将使用的PyObject
来创建和填充的std ::矢量&lt ; unsigned char型&GT;
。 unsigned char型&GT; (或const引用)参数和从巨蟒所提供的参数是一个字符串,如果有一个的std ::矢量&lt出口的C ++函数时,才会发生这种转换。因此,这种自定义转换不会影响导出的函数具有
的std ::字符串
参数。
In context of the question, we want a converter for std::vector<unsigned char>
where converter(PyObject)
returns non-NULL
if PyObject
is a PyString
, and converter(PyObject)
will use PyObject
to create and populate std::vector<unsigned char>
. This conversion will only occur if for exported C++ functions that have a std::vector<unsigned char>
(or a const reference) parameter and the argument provided from python is a string. Therefore, this custom converter will not affect exported functions that have std::string
parameters.
下面是一个完整的例子。我选择使变频器通用,允许多种类型是从Python字符串施工的。凭借其链接的支持,它应该有感觉其他Boost.Python的类型相同。
Here is a complete example. I have opted to make the converter generic to allow multiple types to be constructable from a python string. With its chaining support, it should have the same feel as other Boost.Python types.
#include <iostream>
#include <list>
#include <string>
#include <vector>
#include <boost/foreach.hpp>
#include <boost/python.hpp>
class Hello
{
public:
void printChar(const std::string& str)
{
std::cout << "printChar: " << str << std::endl;
}
void storeFile(const std::vector<unsigned char>& data)
{
std::cout << "storeFile: " << data.size() << ": ";
BOOST_FOREACH(const unsigned char& c, data)
std::cout << c;
std::cout << std::endl;
}
};
/// @brief Type that allows for conversions of python strings to
// vectors.
struct pystring_converter
{
/// @note Registers converter from a python interable type to the
/// provided type.
template <typename Container>
pystring_converter&
from_python()
{
boost::python::converter::registry::push_back(
&pystring_converter::convertible,
&pystring_converter::construct<Container>,
boost::python::type_id<Container>());
return *this;
}
/// @brief Check if PyObject is a string.
static void* convertible(PyObject* object)
{
return PyString_Check(object) ? object : NULL;
}
/// @brief Convert PyString to Container.
///
/// Container Concept requirements:
///
/// * Container::value_type is CopyConstructable from char.
/// * 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;
// 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.
const char* begin = PyString_AsString(object);
data->convertible = new (storage) Container(
begin, // begin
begin + PyString_Size(object)); // end
}
};
BOOST_PYTHON_MODULE(hello)
{
namespace python = boost::python;
// Register PyString conversions.
pystring_converter()
.from_python<std::vector<unsigned char> >()
.from_python<std::list<char> >()
;
python::class_<Hello>("Hello")
// This method takes a string as parameter and print it
.def("printChar", &Hello::printChar)
// This method takes a vector<unsigned char> parameter
.def("storeFile", &Hello::storeFile)
;
}
和例子用法:
>>> from hello import Hello
>>> h = Hello()
>>> h.printChar('abc')
printChar: abc
>>> h.storeFile('def')
storeFile: 3: def
>>> h.storeFile([c for c in 'def'])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
Hello.storeFile(Hello, list)
did not match C++ signature:
storeFile(Hello {lvalue}, std::vector<unsigned char,
std::allocator<unsigned char> >)
有关更多的自定义转换器和C ++的容器,可以阅读这个答案。
For more on custom converters and C++ containers, consider reading this answer.