Я работаю над библиотекой С++ с привязками Python (с использованием boost:: python), представляющими данные, хранящиеся в файле. Большинство моих полутехнических пользователей будут использовать Python для взаимодействия с ним, поэтому мне нужно сделать его максимально возможным как Pythonic. Тем не менее, у меня также будут программисты на С++, использующие API, поэтому я не хочу компрометировать на стороне С++ для размещения привязок Python.
Большая часть библиотеки будет сделана из контейнеров. Чтобы сделать вещи интуитивно понятными для пользователей python, я бы хотел, чтобы они вели себя как списки python, т.е.:
# an example compound class
class Foo:
def __init__( self, _val ):
self.val = _val
# add it to a list
foo = Foo(0.0)
vect = []
vect.append(foo)
# change the value of the *original* instance
foo.val = 666.0
# which also changes the instance inside the container
print vect[0].val # outputs 666.0
Настройка тестирования
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#include <boost/python/register_ptr_to_python.hpp>
#include <boost/shared_ptr.hpp>
struct Foo {
double val;
Foo(double a) : val(a) {}
bool operator == (const Foo& f) const { return val == f.val; }
};
/* insert the test module wrapping code here */
int main() {
Py_Initialize();
inittest();
boost::python::object globals = boost::python::import("__main__").attr("__dict__");
boost::python::exec(
"import test\n"
"foo = test.Foo(0.0)\n" // make a new Foo instance
"vect = test.FooVector()\n" // make a new vector of Foos
"vect.append(foo)\n" // add the instance to the vector
"foo.val = 666.0\n" // assign a new value to the instance
// which should change the value in vector
"print 'Foo =', foo.val\n" // and print the results
"print 'vector[0] =', vect[0].val\n",
globals, globals
);
return 0;
}
Способ shared_ptr
Используя shared_ptr, я могу получить то же поведение, что и выше, но это также означает, что я должен представлять все данные в С++ с помощью общих указателей, что не очень хорошо со многих точек зрения.
BOOST_PYTHON_MODULE( test ) {
// wrap Foo
boost::python::class_< Foo, boost::shared_ptr<Foo> >("Foo", boost::python::init<double>())
.def_readwrite("val", &Foo::val);
// wrap vector of shared_ptr Foos
boost::python::class_< std::vector < boost::shared_ptr<Foo> > >("FooVector")
.def(boost::python::vector_indexing_suite<std::vector< boost::shared_ptr<Foo> >, true >());
}
В моей тестовой установке это дает тот же результат, что и чистый Python:
Foo = 666.0
vector[0] = 666.0
Способ vector<Foo>
Использование вектора непосредственно дает хорошую чистую настройку на стороне С++. Однако результат не ведет себя так же, как чистый Python.
BOOST_PYTHON_MODULE( test ) {
// wrap Foo
boost::python::class_< Foo >("Foo", boost::python::init<double>())
.def_readwrite("val", &Foo::val);
// wrap vector of Foos
boost::python::class_< std::vector < Foo > >("FooVector")
.def(boost::python::vector_indexing_suite<std::vector< Foo > >());
}
Это дает:
Foo = 666.0
vector[0] = 0.0
Что является "неправильным" - изменение исходного экземпляра не изменило значение внутри контейнера.
Надеюсь, я не хочу слишком много
Интересно, что этот код работает независимо от того, какой из двух инкапсуляций я использую:
footwo = vect[0]
footwo.val = 555.0
print vect[0].val
Это означает, что boost:: python может иметь дело с "поддельным совместным владением" (через механизм возврата by_proxy). Есть ли способ достичь того же при вставке новых элементов?
Однако, если ответ отрицательный, мне бы хотелось услышать другие предложения - есть ли пример в наборе инструментов Python, где реализована подобная инкапсуляция коллекции, но которая не ведет себя как список python?
Большое спасибо за прочтение этого:)