Конвертация типов в Boost.Python. Делаем преобразование между привычными типами C++ и Python

от автора

Данная статья не является продолжением повествования об обёртках C++ API. Никаких обёрток сегодня не будет. Хотя по логике это третья часть данного повествования.
Сегодня будет море крови, расчленение существующих типов и магическое превращение их в привычные аналоги в другом языке.
Речь не пойдёт о существующей конвертации между строками, нет, мы напишем свои конвертеры.
Мы превратим привычный datetime.datetime питона в boost::posix_time::ptime библиотеки Boost и обратно, да чёрт с ним, мы вообще всю библиотеку datetime превратим в бустовые типы! А чтобы не было скучно, принесём в жертву встроенный класс массива байт Python 3.x, для него как раз ещё нет конвертера в Boost.Python, а потом зверски используем конвертацию массива байт в новом конвертере питоновского uuid.UUID в boost::uuids::uuid. Да, конвертер можно использовать в конвертере!
Жаждешь крови, Колизей?!..

Вместо введения

Если кто не заметил, Boost.Python делает огромную работу, превращая кучу скаляров в объекты классов Python соответствующего типа. Если вы хотите сравнить, пишите на чистом Си, используйте напрямую C-API, дайте ему посношать свой мозг. Потратьте кучу времени, чтобы понять комфорт современных технологий, удобство мягкого кресла, необходимость горячей ванной и пульта дистанционного управления для телевизора. Любители деревянных лавок, мытья в проруби и лучины, пусть и дальше занимаются лубочным творчеством.
Так вот, есть такое понятие: built-in converters в Boost.Python — встроенные конвертеры типов из Python в C++ и обратно, которые частично реализованы в $(BoostPath)\libs\python\src\converter и $(BoostPath)\boost\python\converter. Их много, они решают где-то около 95% проблем при работе со встроенными типами Python и C++, есть конвертация строк, не идеальная конечно, но если в C++ мы работаем с UTF-8 строками или wide-строками, то всё будет быстро, качественно и незаметно, в смысле удобно в использовании.
Почти всё что не делается встроенными конвертерами, решается обёртками ваших классов. Boost.Python предлагает поистине чудовищно простой путь, описывать обёртки классов, в виде мета-языка, который даже выглядит как класс Python:

class_<Some>( "Some" )     .def( "method_A", &Some::method_A, args( "x", "y", "z" ) )     .def( "method_B", &Some::method_B, agrs( "u", "v" ) ) ; 

Всё здорово, но есть одно но…
… одно большое и замечательное но: и C++, и Python — языки со своими библиотеками. В C++

#include <boost/date_time.hpp> #include <boost/uuid/uuid.hpp> 

является де-факто аналогом в Python:

import datetime import uuid 

Так вот, очень многое в вашем C++ коде уже может быть завязано именно на работу например с классом boost::gregorian::date, а в Python в свою очередь многое завязано на класс datetime.date, его аналог. Работать в Python с обёрткой класса boost::gregorian::date, обёрнутому со всеми методами, перегрузкой операторов и попытка воткнуть его экземпляры вместо привычного datetime.date — это я даже не знаю как называется, это не костыль, это танцы с гранатой. И эта граната рванёт, господа присяжные заседатели. На стороне Python нужно работать со встроенной библиотекой даты и времени.
Если вы читаете это, и смотрите на свой код, где вы через extract достаёте в C++ поля питоновского datetime, то нечего глупо улыбаться, всё описанное абзацем выше относится к вам в не меньше степени. Даже если у вас в C++ свой мега-класс даты/времени, то лучше написать конвертер типа, чем выцеплять их по одному полю в каком-то велосипедном методе.
В общем, если на стороне Python свой тип, а на стороне С++ свой устоявшийся тип, реализующий базовую логику с аналогичной функционально составляющей, то вам нужен конвертер.
Вам действительно нужен конвертер.

Что такое есть конвертер

Конвертер — это некое зарегистрированное в Boost.Python преобразование из типа C++ в тип Python или обратно. На стороне C++ вы пользуетесь привычными типами, в полной уверенности, что в Python это будет соответствующий тип. Собственно конвертеры обычно пишут в обе стороны, но написать преобразование из C++ в Python на порядок проще, сами увидите. Всё дело в том, что создание экземпляра в C++ требует памяти, что является зачастую нетревиальной задачей. Создание объекта в Python задача крайне простая, поэтому начнём с преобразования из C++ в Python.

Конвертация типа из C++ в Python

Для конвертации из C++ в Python вам потребуется структура у которой есть статический метод convert, принимающий ссылку на тип в C++ и возвращающий PyObject*, общий тип для любого объекта использующегося в C-API языка Python и в качестве начинки объекта boost::python::object.
Давайте сразу заведём шаблонную структуру, поскольку мы хотим массовой бойни:

template< typename T > struct type_into_python {     static PyObject* convert( T const& ); }; 

Всё что потребуется — реализовать например для типа boost::posix_time::ptime метод специализации шаблонной структуры:

template<> PyObject* type_into_python<ptime>::convert( ptime const& );  

и зарегистрировать конвертер при объявлении модуля внутри BOOST_PYTHON_MODULE:

    to_python_converter< ptime, type_into_python<ptime> >(); 

Ну хорошо, раз уж я сказал Аз, давайте скажу вам и Буки. Реализация конвертера для boost::posix_time::ptime будет выглядеть приблизительно вот так:

PyObject* type_into_python<ptime>::convert( ptime const& t ) {     auto d = t.date();     auto tod = t.time_of_day();     auto usec = tod.total_microseconds() % 1000000;     return PyDateTime_FromDateAndTime( d.year(), d.month(), d.day(), tod.hours(), tod.minutes(), tod.seconds(), usec ); } 

Важно! При регистрации модуля нам обязательно нужно подключить datetime через C-API:

    PyDateTime_IMPORT;     to_python_converter< ptime, type_into_python<ptime> >(); 

Без строки PyDateTime_IMPORT ничего не взлетит.

Нам в общем повезло в том, что в C-API языка Python есть готовая функция по созданию PyObject* на новый datetime.datetime по его параметрам, по сути аналог конструктора класса datetime. И не повезло, что в Boost такое «забавное» API для класса ptime. Класс получился не совсем самостоятельным, приходится выцеплять из него дату и время, находящиеся там отдельными компонентами, причём время представлено в виде time_duration — аналог не столько datetime.time, сколько скорее datetime.timedelta! Это в общем-то не позволит взаимооднозначно представить типы библиотеки datetime в C++.Ну и совсем неприятно то, что boost::posix_time::time_duration не предоставляет прямого доступа к микросекундам и миллисекундам. Вместо этого приходится либо «хитро» работать с методом fractional_seconds(), либо тупо сделать страшное — взять модуль total_microseconds() % 1000000. Что хуже — я ещё не решил, мне вообще не нравится как сделан time_duration. Мы из него за это сделаем класс datetime.time, а другой схожий класс datetime.timedelta мы пока трогать не будем.

Конвертация из Python в C++

Хе-хе, друзья мои, это реально сложный пункт. Запаситесь валидолом, пристегните ремни.
Всё вроде бы точно так же: делаем шаблон структуры с двумя методами convertible и construct — возможность конвертации и конструктор типа в C++. Собственно всё равно как называются методы, главное сослаться на них при регистрации, удобнее всего это делать в конструкторе нашей шаблонной структуры:

template< typename T > struct type_from_python {     type_from_python()     {         converter::registry::push_back( convertible, construct, type_id<T>() );     }      static void* convertible( PyObject* );     static void construct( PyObject*, converter::rvalue_from_python_stage1_data* ); }; 

Собственно при объявлении модуля будет достаточно вызвать конструктор данной структуры. Ну и, разумеется, нужно реализовать данные методы для каждого конвертируемого типа, например для ptime:

template<> void* type_from_python<ptime>::convertible( PyObject* ); template<> void  type_from_python<ptime>::construct( PyObject*, converter::rvalue_from_python_stage1_data* ); 

Давайте посмотрим сразу на реализацию метода проверки на конвертабельность и метода конструирования ptime:

void* type_from_python<ptime>::convertible( PyObject* obj ) {     return PyDateTime_Check( obj ) ? obj : nullptr; }  void type_from_python<ptime>::construct( PyObject* obj, converter::rvalue_from_python_stage1_data* data ) {     auto storage = reinterpret_cast< converter::rvalue_from_python_storage<ptime>* >( data )->storage.bytes;     date date_only( PyDateTime_GET_YEAR( obj ), PyDateTime_GET_MONTH( obj ), PyDateTime_GET_DAY( obj ) );     time_duration time_of_day( PyDateTime_DATE_GET_HOUR( obj ), PyDateTime_DATE_GET_MINUTE( obj ), PyDateTime_DATE_GET_SECOND( obj ) );     time_of_day += microsec( PyDateTime_DATE_GET_MICROSECOND( obj ) );     new(storage) ptime( date_only, time_of_day );     data->convertible = storage;  } 

С методом convertible всё ясно: ты datetime — проходи, нет — nullptr и на выход.
А вот метод construct будет таким же зубодробительным для абсолютно каждого типа!
Даже если у вас свой тип MyDateTime, вам придётся его создавать по месту через размещающий new там, где вам дадут его разместить! Видите вот этот забавный оператор:

    new(storage) ptime( date_only, time_of_day ); 

Это размещающий new. Он создаёт ваш новый объект в указанном месте. Это самое место нам нужно ещё вычислить, нам предлагают следующий путь получения искомого указателя:

    auto storage = reinterpret_cast< converter::rvalue_from_python_storage<ptime>* >( data )->storage.bytes; 

Я не буду это комментировать. Просто запомните.
Всё остальное — дополнительные вычисления для вызова вполне понятного конструктора несамостоятельного класса ptime.
Не забудьте в конце заполнить ещё одно поле:

    data->convertible = storage; 

Опять же не знаю как это помягче назвать, просто помните, что это важно и поле нужно заполнить. Думайте об этом как о неприятной мелочи перед всеобщим счастьем.
Примеры как это делает кто-то кроме меня можно посмотреть здесь, здесь и здесь на сайте Boost.Python в разделе FAQ.

Преобразование типов datetime в <boost/date_time.hpp> и обратно

Итого, для date и time по отдельности всё довольно просто. Благодаря нашей шаблонной структуре нам осталось лишь добавить реализацию для date и time_duration следующих методов специализаций наших шаблонных структур:

template<> PyObject* type_into_python<date>::convert( date const& ); template<> void*     type_from_python<date>::convertible( PyObject* ); template<> void      type_from_python<date>::construct( PyObject*, converter::rvalue_from_python_stage1_data* );  template<> PyObject* type_into_python<time_duration>::convert( time_duration const& ); template<> void*     type_from_python<time_duration>::convertible( PyObject* ); template<> void      type_from_python<time_duration>::construct( PyObject*, converter::rvalue_from_python_stage1_data* ); 

Задача несложная, сводится к разбиению предыдущих методов на пары для даты и времени по отдельности.
Для boost::gregorian::date и datetime.date:

PyObject* type_into_python<date>::convert( date const& d ) {     return PyDate_FromDate( d.year(), d.month(), d.day() ); }  void* type_from_python<date>::convertible( PyObject* obj ) {     return PyDate_Check( obj ) ? obj : nullptr; }  void type_from_python<date>::construct( PyObject* obj, converter::rvalue_from_python_stage1_data* data ) {     auto storage = reinterpret_cast< converter::rvalue_from_python_storage<date>* >( data )->storage.bytes;     new(storage) date( PyDateTime_GET_YEAR( obj ), PyDateTime_GET_MONTH( obj ), PyDateTime_GET_DAY( obj ) );     data->convertible = storage;  } 

И для boost::posix_time::time_duration и datetime.time:

PyObject* type_into_python<time_duration>::convert( time_duration const& t ) {     auto usec = t.total_microseconds() % 1000000;     return PyTime_FromTime( t.hours(), t.minutes(), t.seconds(), usec ); }  void* type_from_python<time_duration>::convertible( PyObject* obj ) {     return PyTime_Check( obj ) ? obj : nullptr; }  void type_from_python<time_duration>::construct( PyObject* obj, converter::rvalue_from_python_stage1_data* data ) {     auto storage = reinterpret_cast< converter::rvalue_from_python_storage<time_duration>* >( data )->storage.bytes;     time_duration* t = new(storage) time_duration( PyDateTime_TIME_GET_HOUR( obj ), PyDateTime_TIME_GET_MINUTE( obj ), PyDateTime_TIME_GET_SECOND( obj ) );     *t += microsec( PyDateTime_TIME_GET_MICROSECOND( obj ) );     data->convertible = storage;  } 

Регистрация всего этого добра в нашем модуле будет выглядеть примерно так:

BOOST_PYTHON_MODULE( ... ) {     ...     PyDateTime_IMPORT;      to_python_converter< ptime, type_into_python<ptime> >();     type_from_python< ptime >();      to_python_converter< date, type_into_python<date> >();     type_from_python< date >();      to_python_converter< time_duration, type_into_python<time_duration> >();     type_from_python< time_duration >();     ... } 

Проверяем работу с конвертацией даты и времени

Пора проверить в деле нашу мегаконвертацию, заведём всякие ненужные функции которые на входе принимают дату/время и на выходе тоже возвращают дату/время.

ptime tomorrow(); ptime day_before( ptime const& the_moment );  date last_day_of_this_month(); date year_after( date const& the_day );  time_duration delta_between( ptime const& at, ptime const& to ); time_duration plus_midday( time_duration const& the_moment ); 

Объявим их в нашем модуле, чтобы вызывать из Python:

    def( "tomorrow", tomorrow );     def( "day_before", day_before, args( "moment" ) );     def( "last_day_of_this_month", last_day_of_this_month );     def( "year_after", year_after, args( "day" ) );     def( "delta_between", delta_between, args( "at", "to" ) );     def( "plus_midday", plus_midday, args( "moment" ) ); 

Путь эти наши функции делают следующее (хотя на самом деле это уже не важно, важны типы на входе/выходе):

ptime tomorrow() {     return microsec_clock::local_time() + days( 1 ); }  ptime day_before( ptime const& that ) {     return that - days( 1 ); }  date last_day_of_this_month() {     date today = day_clock::local_day();     date next_first_day = (today.month() == Dec) ? date( today.year() + 1, 1, 1 ) : date( today.year(), today.month() + 1, 1 );     return next_first_day - days( 1 ); }  date year_after( date const& the_day ) {     return the_day + years( 1 ); }  time_duration delta_between( ptime const& at, ptime const& to ) {     return to - at; }  time_duration plus_midday( time_duration const& the_moment ) {     return time_duration( 12, 0, 0 ) + the_moment; } 

В частности вот такой вот несложный скрипт (на Python 3.x):

from someconv import * from datetime import * # test datetime.datetime <=> boost::posix_time::ptime t = tomorrow(); print( 'Tomorrow at same time:', t ) for _ in range(3): t = day_before(t); print( 'Day before that moment:', t ) # test datetime.date <=> boost::gregorian::date d = last_day_of_this_month(); print( 'Last day of this month:', d ) for _ in range(3): d = year_after(d); print( 'Day before that day:', d ) # test datetime.time <=> boost::posix_time::time_duration at = datetime.now() to = at + timedelta( seconds=12*60*60 ) dt = delta_between( at, to ) print( "Delta between '{at}' and '{to}' is '{dt}'".format( at=at, to=to, dt=dt ) ) t0 = time( 6, 30, 0 ) t1 = plus_midday( t0 ) print( t0, "plus midday is:", t1 ) 

Должен отработать корректно и завершиться примерно вот с выводом корректных дат и времён. Тестовый скрипт разумеется будет приложен. (Вывод не пишу, чтобы не палиться во сколько это было написано!)
Можете в принципе не стесняться и писать свои тестовые функции, они все отработают как надо, если вы всё сделали правильно.
В крайнем случае в конце выложу ссылку на проект вместе с тестовым скриптом.

Байтовый массив в виде вектора байт в C++

Вообще говоря приведённый ниже пример вреден чрезвычайно. Стандартный шаблон std::vector по типу с разрядностью ниже int будет крайне неэффективным. Проигрыш при копировании и, как следствие, при vector::resize() будет катастрофичен, просто потому, что копирование будет производиться поэлементно. Со всеми включенными оптимизациями это приведёт к потерям до 170% при простом копировании в сравнении с memcpy() (замерялось в Release-сборке MSVS v10). Что не особо приятно для частоиспользуемого фрагмента кода. Особенно когда копирования не видно, а иногда неявно происходит resize(). Возникают «занятные» проседания по производительности, в том смысле что будет чем заняться, отлавливая тормоза в большой системе.

Пример ниже чисто академический, если вам где-либо нужна маниакальная оптимизация кода и вы именно за этим пишете часть кода модуля на С++. Если же вам побоку на производительность, смело можете использовать данное преобразование.
Для Python 2.x данный раздел неактуален в принципе. Тогда байтовые массивы назывались строками. Куда интереснее будет почитать про работу с unicode и преобразование его в стандартную строку C++ здесь в PyWiki.
Зато для Python 3.x данное преобразование позволит сократить громадный кусок кода c кучей C-API до использования обычного vector (byte — беззнаковое 8-битное целое — uint8_t).

Итак, снова используем наши замечательные шаблонные структуры и радуемся:

typedef uint8_t byte; typedef vector<byte> byte_array; ... template<> PyObject* type_into_python<byte_array>::convert( byte_array const& ); template<> void*     type_from_python<byte_array>::convertible( PyObject* ); template<> void      type_from_python<byte_array>::construct( PyObject*, converter::rvalue_from_python_stage1_data* ); 

Всё так же добавляем в объявление нашего модуля регистрацию конвертеров:

BOOST_PYTHON_MODULE( ... ) {     ...     to_python_converter< byte_array, type_into_python<byte_array> >();     type_from_python< byte_array >(); } 

И простейшая реализация, используем просто знание C-API объекта PyBytes и работаем с методами std::vector:

PyObject* type_into_python<byte_array>::convert( byte_array const& ba ) {     const char* src = ba.empty() ? "" : reinterpret_cast<const char*>( &ba.front() );     return PyBytes_FromStringAndSize( src, ba.size() ); }  void* type_from_python<byte_array>::convertible( PyObject* obj ) {     return PyBytes_Check( obj ) ? obj : nullptr; }  void type_from_python<byte_array>::construct( PyObject* obj, converter::rvalue_from_python_stage1_data* data ) {     auto storage = reinterpret_cast< converter::rvalue_from_python_storage<byte_array>* >( data )->storage.bytes;     byte* dest; Py_ssize_t len;     PyBytes_AsStringAndSize( obj, reinterpret_cast<char**>( &dest ), &len );     new(storage) byte_array( dest, dest + len );     data->convertible = storage;  } 

Вряд ли потребуются дополнительные комментарии, за знаниями C-API объекта PyBytes отправлю вот сюда.

Преобразуем uuid.UUID в boost::uuids::uuid и обратно

Вы будете смеятся, но мы до того упростили себе работу, создав те два шаблона в самом начале, что опять же всё сведётся к реализации тройки методов:

using namespace boost::uuids; ... template<> PyObject* type_into_python<uuid>::convert( uuid const& ); template<> void*     type_from_python<uuid>::convertible( PyObject* ); template<> void      type_from_python<uuid>::construct( PyObject*, converter::rvalue_from_python_stage1_data* ); 

Привычно добавляем в объявление модуля две новых строчки — регистрацию конвертации туда и обратно:

    to_python_converter< uuid, type_into_python<uuid> >();     type_from_python< uuid >(); 

А теперь самое интересное, C-API тут нам не поможет, скорее помешает, проще всего действовать через boost::python::import собственно модуля Python «uuid» и класса «UUID» этого же модуля.

static object py_uuid = import( "uuid" ); static object py_uuid_UUID = py_uuid.attr( "UUID" );  PyObject* type_into_python<uuid>::convert( uuid const& u ) {     return incref( py_uuid_UUID( object(), byte_array( u.data, u.data + sizeof(u.data) ) ).ptr() ); }  void* type_from_python<uuid>::convertible( PyObject* obj ) {     return PyObject_IsInstance( obj, py_uuid_UUID.ptr() ) ? obj : nullptr; }  void type_from_python<uuid>::construct( PyObject* obj, converter::rvalue_from_python_stage1_data* data ) {     auto storage = reinterpret_cast< converter::rvalue_from_python_storage<uuid>* >( data )->storage.bytes;     byte_array ba = extract<byte_array>( object( handle<>( borrowed( obj ) ) ).attr( "bytes" ) );     uuid* res = new(storage) uuid;     memcpy( res->data, &ba.front(), ba.size() );     data->convertible = storage; } 

Уж извините, что использовал глобальные переменные, обычно это делается в синглтоне с Py_Initialize() и Py_Finalize() в конструкторе и деструкторе соответственно. Но раз уж тут у нас пример чисто учебный и используется пока только из Python, то можно обойтись таким быдлоподходом, ещё раз простите, но так код понятнее.

Поскольку поведение в этих методах сильно отличается от всего вышеописанного, надо подробнее описать, что собственно происходит.
В py_uuid мы сохранили объект подключенного модуля uuid из стандартной библиотеки Python.
В py_uuid_UUID мы сохранили объект класса uuid.UUID. Именно сам класс как таковой. Применение скобок к данному объекту приведёт к вызову конструктора и созданию объекта данного типа. Что мы впоследствии и сделаем. Однако сам этот класс как таковой нам ещё пригодиться для метода convertible — проверки типа аргумента, является ли объект UUID’ом.

В сторону Python из C++ всё понятно — просто вызываем конструктор, в первый параметр передаём None (дефолтный конструктор boost::python::object создаст как раз None), во второй уходит наш байтовый массив из предыдущего раздела. Если у вас Python 2.x код немного поменяется и упроститься, там достаточно передать строку и сделать вид, что это байтовый массив.

При проверке Python-объекта на конвертабельность нам здорово помогает функция PyObject_IsInstance().
Указатель PyObject* типа uuid.UUID берём с помощью метода ptr() класса boost::python::object. Вот тут нам и пригодился объект класса как таковой. По факту классы в Python такие же объекты. И это здорово. Спасибо за столь логичный и понятный язык.

Вот код преобразования из Python в C++ уже ничего не понятно, что происходит на этой строчке:

    byte_array ba = extract<byte_array>( object( handle<>( borrowed( obj ) ) ).attr( "bytes" ) ); 

Здесь на самом деле всё предельно просто. Из объекта uuid.UUID пришедшего как PyObject* мы создаём полноценный boost::python::object. Обратите внимание на конструкцию handle<>( borrowed( obj ) ) — здесь очень важно не потерять вызов borrowed, иначе наш свежий object грохнет в деструкторе переданный объект.
Итак, мы получили из PyObject* объект boost::python::object по ссылке на аргумент типа uuid.UUID. Берём у нашего объекта атрибут bytes, вытаскиваем из него byte_array через extract. Всё, у нас есть содержимое.
Любители сделать всё через сериализацию-десериализацию могут поиспражняться через преобразование в строку и обратно. Всякий lexical_cast() им в помощь и камень на шею. Помните, что создание строк и сериализация в C++ по сути очень дорогая операция.
Пользователи Python 2.x сразу же получат байты в виде строки. Такие уж раньше были строки, как и в C/C++, по сути через char*.
В общем дальше всё просто, заполняем массив, уж извините за небезопасное копирование, и передаём заполненный объект обратно в C++.

Проверяем работу преобразований массива байт и UUID

Давайте заведём ещё несколько функций гоняющих туда-сюда наши типы между C++ и Python:

byte_array string_to_bytes( string const& src ); string bytes_to_string( byte_array const& src );  uuid random_uuid(); byte_array uuid_bytes( uuid const& src ); 

Опишем их в нашем модуле для вызова из Python:

BOOST_PYTHON_MODULE( someconv ) {     ...     def( "string_to_bytes", string_to_bytes, args( "src" ) );     def( "bytes_to_string", bytes_to_string, args( "src" ) );     def( "random_uuid", random_uuid );     def( "uuid_bytes", uuid_bytes, args( "src" ) );     ... } 

Собственно поведение их не столь важно, однако давайте честно опишем их реализацию для наглядности результата:

byte_array string_to_bytes( std::string const& src ) {     return byte_array( src.begin(), src.end() ); }  string bytes_to_string( byte_array const& src ) {     return string( src.begin(), src.end() ); }  uuid random_uuid() {     static random_generator gen_uuid;     return gen_uuid(); }  byte_array uuid_bytes( uuid const& src ) {     return byte_array( src.data, src.data + sizeof(src.data) ); } 

В общем и целом такой тестовый скрипт (на Python 3.x):

from someconv import * from uuid import * ... # test bytes <=> std::vector<uint8_t> print( bytes_to_string( b"I_must_be_string" ) ) print( string_to_bytes( "I_must_be_byte_array" ) ) print( bytes_to_string( " - Привет!".encode() ) ) print( string_to_bytes( " - Пока!" ).decode() ) print( bytes_to_string( string_to_bytes( " - Ну пока!" ) ) ) # test uuid.UUID <=> boost::uuids::uuid u = random_uuid() print( 'Generated UUID (C++ module):', uuid_bytes(u) ) print( 'Generated UUID (in Python): ', u.bytes) 

Должен корректно отработать и выдать результат что-то вроде:

I_must_be_string b'I_must_be_byte_array'  - Привет!  - Пока!  - Ну пока! Generated UUID (C++ module): b'\xf1B\xdb\xa9<lL\x9d\x9a\xfd\xf3\xe9\x9f\xa6\x9aT' Generated UUID (in Python):  b'\xf1B\xdb\xa9<lL\x9d\x9a\xfd\xf3\xe9\x9f\xa6\x9aT' 

Кстати, если вы для проверки возьмёте и удалите borrowed из ковертации UUID из Python в C++, то свалитесь ровно на последней строчке, так как объект будет уже уничтожен и не у чего будет брать свойство bytes.

Итого

Мы научились не только писать конвертеры, но и обобщать их, сводить трудовые затраты при их написании к минимуму и использовать один из другого. Собственно мы уже знаем что это, как этим пользоваться и где оно жизненно необходимо.
Ссылка на проект лежит здесь (~207 KB). Проект MSVS v11, настроен на сборку с Python 3.3 x64.

Полезные ссылки

Документация Boost.Python
Как написать конвертер строки
Работа с Unicode в Python 2.x
Преобразование массивов между C++ и Python
Ещё вариант конвертации даты/времени

ссылка на оригинал статьи http://habrahabr.ru/post/168827/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *