Данная статья не является продолжением повествования об обёртках 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)libspythonsrcconverter и $(BoostPath)boostpythonconverter. Их много, они решают где-то около 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'xf1Bxdbxa9<lLx9dx9axfdxf3xe9x9fxa6x9aT'
Generated UUID (in Python): b'xf1Bxdbxa9<lLx9dx9axfdxf3xe9x9fxa6x9aT'
Кстати, если вы для проверки возьмёте и удалите borrowed из ковертации UUID из Python в C++, то свалитесь ровно на последней строчке, так как объект будет уже уничтожен и не у чего будет брать свойство bytes.
Итого
Мы научились не только писать конвертеры, но и обобщать их, сводить трудовые затраты при их написании к минимуму и использовать один из другого. Собственно мы уже знаем что это, как этим пользоваться и где оно жизненно необходимо.
Ссылка на проект лежит здесь (~207 KB). Проект MSVS v11, настроен на сборку с Python 3.3 x64.
Полезные ссылки
Документация Boost.Python
Как написать конвертер строки
Работа с Unicode в Python 2.x
Преобразование массивов между C++ и Python
Ещё вариант конвертации даты/времени
Автор: Qualab