На ум приходят два возможных подхода.
Давайте предположим, что мы пытаемся незаметно представить следующий класс C ++:
class MyClass
{
public:
MyClass(std::list<std::string> messages)
: msgs(std::move(messages))
{
}
void dump() const
{
std::cout << "Messages:\n";
for (auto const& msg : msgs) {
std::cout << msg << "\n";
}
}
// NB: This would be better returned as const ref
// but I have issues exposing that to Python
// that I have yet to solve
std::list<std::string> messages() const
{
return msgs;
}
private:
std::list<std::string> msgs;
};
Если единственное место, где нам нужно иметь дело с std::list
, это конструктор, то самый простой подход - написать небольшую "фабричную" функцию, которая будет
- Взять список Python в качестве входных данных.
- Создайте
std::list
и заполните его значениями из списка Python.
- Создание экземпляра
MyClass
с std::list
.
- Вернуть этот экземпляр
MyClass
.
Мы будем использовать shared_ptr
для управления памятью. Чтобы легко инициализировать std::list
, мы можем воспользоваться boost::python::stl_input_iterator
.
boost::shared_ptr<MyClass> create_MyClass(bp::list const& l)
{
std::list<std::string> messages{ bp::stl_input_iterator<std::string>(l)
, bp::stl_input_iterator<std::string>() };
return boost::make_shared<MyClass>(messages);
}
Как только мы получим эту функцию, мы представим ее вместо исходного конструктора MyClass
. Для этого сначала нужно отключить любые привязки конструктора по умолчанию, поэтому мы используем boost::python::no_init
. В python конструкторы - это просто функции с именем __init__
. Наконец, нам нужно использовать явно недокументированную функцию boost::python::make_constructor
для создания объекта функции appriate.
BOOST_PYTHON_MODULE(so07)
{
bp::class_<MyClass, boost::noncopyable, boost::shared_ptr<MyClass>>("MyClass", bp::no_init)
.def("__init__", bp::make_constructor(create_MyClass))
.def("dump", &MyClass::dump)
;
}
Стенограмма:
>>> import so07
>>> test = so07.MyClass(['a','b','c'])
>>> test.dump()
Messages:
a
b
c
Если мы хотим использовать std::list
в других контекстах, то написание отдельных функций-оберток для работы с переводом быстро выйдет из-под контроля. Чтобы избежать этого, мы можем зарегистрировать пользовательские конвертеры, которые позволят Boost.Python автоматически конвертировать списки Python, содержащие строки, в std::list<std::string>
объекты и наоборот.
Переход от C ++ к python довольно прост - просто создайте boost::python::list
и затем добавьте все элементы из списка C ++. Мы можем зарегистрировать этот конвертер используя boost::python::to_python_converter
.
struct std_list_to_python
{
static PyObject* convert(std::list<std::string> const& l)
{
bp::list result;
for (auto const& value : l) {
result.append(value);
}
return bp::incref(result.ptr());
}
};
Переход с Python на C ++ является двухэтапным процессом. Прежде всего, нам нужна функция, чтобы определить, является ли ввод действительным кандидатом для преобразования. В этом случае объект должен быть списком Python, а каждый его элемент должен быть строкой Python. Второй этап состоит в создании на месте std::list
и последующем заполнении его элементами из списка Python. Мы регистрируем этот конвертер, используя boost::python::converter::registry::push_back
.
struct pylist_converter
{
static void* convertible(PyObject* object)
{
if (!PyList_Check(object)) {
return nullptr;
}
int sz = PySequence_Size(object);
for (int i = 0; i < sz; ++i) {
if (!PyString_Check(PyList_GetItem(object, i))) {
return nullptr;
}
}
return object;
}
static void construct(PyObject* object, bp::converter::rvalue_from_python_stage1_data* data)
{
typedef bp::converter::rvalue_from_python_storage<std::list<std::string>> storage_type;
void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
data->convertible = new (storage) std::list<std::string>();
std::list<std::string>* l = (std::list<std::string>*)(storage);
int sz = PySequence_Size(object);
for (int i = 0; i < sz; ++i) {
l->push_back(bp::extract<std::string>(PyList_GetItem(object, i)));
}
}
};
Наш модуль будет выглядеть следующим образом:
BOOST_PYTHON_MODULE(so07)
{
bp::to_python_converter<std::list<std::string>, std_list_to_python>();
bp::converter::registry::push_back(&pylist_converter::convertible
, &pylist_converter::construct
, bp::type_id<std::list<std::string>>());
bp::class_<MyClass, boost::noncopyable>("MyClass", bp::init<std::list<std::string>>())
.def("dump", &MyClass::dump)
.def("messages", &MyClass::messages)
;
}
Стенограмма:
>>> import so07
>>> test = so07.MyClass(['a','b','c'])
>>> test.dump()
Messages:
a
b
c
>>> test.messages()
['a', 'b', 'c']
Ссылка:
Полный код:
#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <list>
#include <iostream>
namespace bp = boost::python;
class MyClass
{
public:
MyClass(std::list<std::string> messages)
: msgs(std::move(messages))
{
}
void dump() const
{
std::cout << "Messages:\n";
for (auto const& msg : msgs) {
std::cout << msg << "\n";
}
}
std::list<std::string> messages() const
{
return msgs;
}
private:
std::list<std::string> msgs;
};
#if 1
boost::shared_ptr<MyClass> create_MyClass(bp::list const& l)
{
std::list<std::string> messages{ bp::stl_input_iterator<std::string>(l)
, bp::stl_input_iterator<std::string>() };
return boost::make_shared<MyClass>(messages);
}
BOOST_PYTHON_MODULE(so07)
{
bp::class_<MyClass, boost::noncopyable, boost::shared_ptr<MyClass>>("MyClass", bp::no_init)
.def("__init__", bp::make_constructor(create_MyClass))
.def("dump", &MyClass::dump)
;
}
#else
struct std_list_to_python
{
static PyObject* convert(std::list<std::string> const& l)
{
bp::list result;
for (auto const& value : l) {
result.append(value);
}
return bp::incref(result.ptr());
}
};
struct pylist_converter
{
static void* convertible(PyObject* object)
{
if (!PyList_Check(object)) {
return nullptr;
}
int sz = PySequence_Size(object);
for (int i = 0; i < sz; ++i) {
if (!PyString_Check(PyList_GetItem(object, i))) {
return nullptr;
}
}
return object;
}
static void construct(PyObject* object, bp::converter::rvalue_from_python_stage1_data* data)
{
typedef bp::converter::rvalue_from_python_storage<std::list<std::string>> storage_type;
void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
data->convertible = new (storage) std::list<std::string>();
std::list<std::string>* l = (std::list<std::string>*)(storage);
int sz = PySequence_Size(object);
for (int i = 0; i < sz; ++i) {
l->push_back(bp::extract<std::string>(PyList_GetItem(object, i)));
}
}
};
BOOST_PYTHON_MODULE(so07)
{
bp::to_python_converter<std::list<std::string>, std_list_to_python>();
bp::converter::registry::push_back(&pylist_converter::convertible
, &pylist_converter::construct
, bp::type_id<std::list<std::string>>());
bp::class_<MyClass, boost::noncopyable>("MyClass", bp::init<std::list<std::string>>())
.def("dump", &MyClass::dump)
.def("messages", &MyClass::messages)
;
}
#endif