Уведомления

Группа в Telegram: @pythonsu

#1 Ноя. 23, 2009 23:10:52

pasaranax
От:
Зарегистрирован: 2009-06-13
Сообщения: 574
Репутация: +  0  -
Профиль   Отправить e-mail  

boost.python и shared libraries

Мне нужно сделать обертку для библиотеки, которая в свою очередь использует другие разделяемые библиотеки. В этом случае надо будет что-то делать с теми, или мне достаточно будет сделать только модуль для нужной мне?



Офлайн

#2 Ноя. 24, 2009 01:08:51

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

boost.python и shared libraries

Все зависит только от оборачиваемого кода. Забудьте на минуту о Py++ и думайте только о классах/функциях. Так, если в параметрах методов или базовых классах есть те, что лежат “снаружи” вашей библиотеки - прийдется сделать обертки и для них. Если вы “неудобный” метод не собираетесь использовать из питона - просто его не заворачивайте, и ничего делать не нужно (Py++ можно накормить списками исключений).



Офлайн

#3 Ноя. 28, 2009 01:40:55

pasaranax
От:
Зарегистрирован: 2009-06-13
Сообщения: 574
Репутация: +  0  -
Профиль   Отправить e-mail  

boost.python и shared libraries

А Pyste еще можно пользоваться или он уже неработоспособен?



Офлайн

#4 Ноя. 28, 2009 21:39:44

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

boost.python и shared libraries

Не знаю насчет работоспособности - наверное все еще гож.
Py++ намного лучше.
А вообще - это всего лишь генераторы кода. Облегчают жизнь, но не более.
Настоятельно рекомендую разобраться с самим boost.python во избежание недоразумений - тогда и с генераторами проблем не будет.



Офлайн

#5 Ноя. 29, 2009 22:13:03

pasaranax
От:
Зарегистрирован: 2009-06-13
Сообщения: 574
Репутация: +  0  -
Профиль   Отправить e-mail  

boost.python и shared libraries

Андрей Светлов
Настоятельно рекомендую разобраться с самим boost.python во избежание недоразумений - тогда и с генераторами проблем не будет.
Спасибо за дельный совет, похоже, я и правда пытаюсь прыгнуть через ступень.
С разделяемыми библиотеками все оказалось проще некуда, с наследованием тоже разобрался. Сейчас разбираюсь с call policies, по ним дается мало примеров. Например, я не нашел примеров, где нет возвращаемого значения, и где нет входных параметров. Можете помочь на примере?
class Item : public WorldObject {
private:
WorldObject* m_owner;
public:
WorldObject* getOwner() { return m_owner; };
void setOwner(WorldObject* value) { m_owner = value; };
};
    class_< Item, bases< WorldObject > >("Item")
.add_property("owner", &Item::getOwner, &Item::setOwner) // как правильно задать property в данном случае?
.def("getOwner", &Item::getOwner, return_internal_reference<>())
.def("setOwner", &Item::setOwner);
доб: кажется, с методами я разобрался (?), а вот как задать property пока не понял

п.с. ничего, если я еще по ходу буду задавать вопросы? :)



Отредактировано (Ноя. 29, 2009 22:35:24)

Офлайн

#6 Ноя. 30, 2009 01:37:02

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

boost.python и shared libraries

Что вас в property смущает? На первый взгляд все хорошо.

А с методами - нет. Вернее, скорее всего генерируется не тот код, который вам нужен.
Поздравляю - вы добрались до call policies. Тема довольно непростая.
Когда метод (функция-член в терминологии С++) или функция верхнего уровня не имеют аргументов - то они их не имеют. Нет, соответственно, и call policy.
Если функция ничего не возвращает - она возвращает None (как и в pure python, между прочим). И указывать call policy тоже не нужно - boost.python сам разберется.

Идем дальше. Нужно понимать, что все преобразования типов делаются через to_python_converter/from_python_converter.
Это довольно сложная система правил, которая из статической информации о типе и call/return policies и динамическом знании о настоящем типе аргумента выбирает подходящий преобразователь.
Все очень гибко настраивается и расширяется - да только не всегда понятно изначально, в какую сторону следует копать.

Для примитивных типов (числа, строки и проч. неизменяемые объекты) - все хорошо и просто.
Можно позволить себе при пересечении барьера между яыками (не важно, в какую сторону) создавать копию.
Число - оно везде число, и подвоха от него не ждут. Думаю, все понятно.
Практически то же самое для простых классов на С++ - нужно определить конструктор копированя/оператор присваивания - и все работает.
Возможно, потом потребуется оптимизация - но это уже следующий шаг.
Делаем систему простой и рабочей, а потом оптимизируем.
Кстати, Гвидо в http://python-history.blogspot.com дает немало интересных примеров в пользу такого подхода - и приводит случаи, когда его попытки преждевременной оптимизации вели к ошибкам в архитектуре языка, искорененным лишь в Python 3 (и то не до конца).

Сложности начинаются с изменяемыми объектами, да еще и завязанными в различные структуры данных типа “граф”.
Увидели указатель - повод насторожиться.
Есть еще некоторое количество промежуточных случаев, но сейчас речь о них не пойдет.

Главная проблема: Питон умеет считать ссылки для своих объектов, автоматически удаляя ненужные - а С/С++ нет.

Берем ваш пример: вызов Item.setOwner(WorldObject* value) из Питона.

Ключевые вопросы: кто создает value, кто им владеет и кто удаляет.
Поясню: если value создается С++ и удаляется им же - то как Питон узнает, что в его item owner уже не существует?
Или другой случай: мы удаляем item. value тоже должно быть удалено, или его “держит” что-то на стороне С++?
call policies помогают прописать правила для каждого конкретного случая, но решает все же программист.

Можно строить разные системы соглашений. Мой опыт говорит, что чем система проще и единообразней - тем она надежней.

Ошибки стоят дорого: в лучшем случае это необъяснимое падение программы по general protection fault.
Как правило, с поврежденным стеком - и даже postmortem debugging не может помочь. Объекты были повреждены задолго до вызова, “положившего” систему.
Может быть и гораздо хуже: повреждается “чужая” память, а программа продолжает работать. Выдавая совсем уж “чудесные” результаты.
Ловля нарушителя может занять месяцы - без гарантированного результата.

В разное время я использовал два решения для этой проблемы.

Первое - очень простое. С++ и Питон равноправны. Чтобы заставить их разделять владение объектом и работать совместно - нужно избавиться от простых указателей.
Вместо них следует применять boost::shared_ptr. При создании оберток использует shared_ptr в качестве HeldType template argument.
Т.е. код выглядит так:

class Item : public WorldObject {
private:
boost::shared_ptr<WorldObject> m_owner;
public:
boost::shared_ptr<WorldObject> getOwner() { return m_owner; };
void setOwner(boost::shared_ptr<WorldObject> value) { m_owner = value; };
};

class_<Item, bases<WorldObject>, boost::shared_ptr<WorldObject>, boost::noncopyable>("Item")
.add_property("owner", &Item::getOwner, &Item::setOwner)
.def("getOwner", &Item::getOwner, return_internal_reference<>())
.def("setOwner", &Item::setOwner);
Скорее всего, все будет немного сложнее и чтобы избежать “колец” прийдется в Item иметь m_owner как boost::weak_ptr<WorldObject> с соответствующими приведениями - но это уже очевидные детали.
Достоинства очевидны - все очень просто.
Недостатки тоже есть:
- переработка исходного кода (ее так или иначе трудно избежать)
- дополнительный уровень связывания (небольшие тормоза из-за лишнего разименовывания указателя)
- HeldType может быть только один (хотя никто не навязывает именно shared_ptr, можно использовать любой класс, который удовлетворяет контракту).
- иногда все же на стороне С++ нужен boost::python::object, который из shared_ptr получить нельзя.
- есть и другие, не столь очевидные сложности.

Но в целом подход очень рабочий.

Вариант номер два более замысловат.
Отделяем bp::object от того класса, который он на самом деле описывает. Храним в себе именно bp::object для того, чтобы Питон считал ссылки за нас.
Создаем разделяемые объекты только через Питоновский интерфейс - или из питона напрямую, или вызывая нашу обертку
    PythonItem = class_<Item, bases<WorldObject>("Item") ...;

bp::object item = PythonItem();
Сам класс выглядит как
class Item : public WorldObject {
private:
bp::object py_owner;
WorldObject* m_owner;
public:
bp::object getOwner() { return py_owner; };
void setOwner(bp::object value) {
bp::extract<WorldObject*> extractor = bp::extract<WorldObject*>(value);
if(!extractor.check())
// выбросить исключение
py_owner = value;
m_owner = extractor();
};
};
Получаем “простые указатели” для С++ и одновременно поддержку Питоновской модели памяти.
Можно сделать ItemHolder, унаследовать его от Item и вставить всю Питоновскую специфику в него. Использовать ItemHoder как HeldType.

Можно придумать что-нибудь еще - но следует понимать главную проблему: простые указатели не умеют разделять владение и не содержат счетчика ссылок.
Поэтому приходится создавать некий механизм для этого - или как соглашение, или в явно использовать на стороне С++ “умные указатели” в любом виде.

P.S. Если что непонятно - спрашивайте.



Офлайн

#7 Ноя. 30, 2009 12:20:52

pasaranax
От:
Зарегистрирован: 2009-06-13
Сообщения: 574
Репутация: +  0  -
Профиль   Отправить e-mail  

boost.python и shared libraries

А совсем без изменений кода не обойтись? Мне, просто, нужно будет поддерживать эту обертку для постоянно развивающегося кода на С++. Принуждать коллегу бустифицировать его код не хочется, значит придется самому поддерживать набор патчей, что тоже не совсем приятно.
Первый способ выглядит заметно привлекательнее, во втором больно много переделок. Если в первом изменения касаются только объявления, но не реализации, и они достаточно однообразны, то можно ли автоматизировать этот процесс, например, Py++ такого не умеет? Или там будут встречаться нетривиальные ситуации?


А в чем ошибка в моем варианте оборачивания тех двух методов? Я проверил вот по такому коду в питоне, висячей ссылки не получается:

import World

npc = World.NPC()
npc.Id = 10
item = World.Item()
item.setOwner(npc)
del npc
print item.getOwner().Id
а если убрать call policy, то вылезает ошибка компиляции, и при задании property тоже ошибка компиляции “не найден метод преобразования…”



Отредактировано (Ноя. 30, 2009 14:22:45)

Офлайн

#8 Дек. 4, 2009 01:40:48

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

boost.python и shared libraries

Может, в вашем случае можно код не менять - я же его не видел. Остается только гадать по облакам.
Автоматом - не выйдет. Даже в тривиальном примере потребовался weak_ptr - а будут случаи и посложнее.
Проще с коллегами разобраться :) - или найти общий язык/взаимопонимание.

Смотрите сами - я лишь попробовал указать на возможные скользкие места.



Офлайн

#9 Дек. 4, 2009 02:41:39

pasaranax
От:
Зарегистрирован: 2009-06-13
Сообщения: 574
Репутация: +  0  -
Профиль   Отправить e-mail  

boost.python и shared libraries

Кажется, я разобрался по большей части. После того, как поработал с простым boost.python, с py++ стало гораздо легче.
Непонятно только пока, как быть, когда функции возвращают или принимают контейнеры из STL, но тут мы обошлись небольшим классом-оберткой на с++, который предоставляет простейший интерфейс для доступа к элементам контейнеров.
И еще какое-то странное предупреждение вылезает сразу при импорте. При чем только для класса NPC, но не для его братьев или суперклассов.

/home/soifong/workspace/Core-Jam/main.py:2: RuntimeWarning: to-Python converter for NPC* already registered; second conversion method ignored.
import Core



Офлайн

#10 Дек. 4, 2009 04:41:17

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

boost.python и shared libraries

Правильной дорогой идете, товарищ.
От простого к сложному - и только так.
Я имею изрядный опыт возни с boost::python, но не могу перелить вам все сразу - сами должны попробовать.
Могу только подсказывать.

Для интерфейса к контейнерам, полагаю, все уже придумали за нас: http://www.boost.org/doc/libs/1_41_0/libs/python/doc/v2/indexing.html
Или indexing suite чем-то не устроил?

По поводу предупреждения: это может быть серьезно, но легко лечится.
Скорее всего вы именно пытаетесь зарегистрировать NPC класс дважды.
Может, вы поместили обертку в .h файл и включаете ее в несколько .cpp (думать или опция gcc -E)
Или несколько .so пытаются обернуть один и тот же класс.

В любом случае:
- используйте boost.python только как .so - он содержит shared data. Отказ от правила чреват последствиями (особенно если до конца не разобрались).
- оборачивайте класс только один раз для всей программы - иначе будут очень неприятные ошибки.

P.S. Если ваши сотрудники уже используют stl (что можно только приветствовать) - почему избегают boost.
Ведь это “неофициальное расширение stl”, библиотеки из которого одна за одной в stl переходят с каждой новой редакцией Стандарта.
Слово “неофициальное” отражает лишь тот факт, что Коммитет Стандарта С++ очень инерционен и принимает изменения крайне медленно - boost развивается куда быстрее.
Но “публичный” boost - очень надежная и стабильная штука, весьма красиво сделанная (в отличие от Стандартной Библиотеки Питона), превосходно поддерживаемая и т.д. В списке библиотек, которые пока не стали частью boost (отклонены, направлены на доработку, обсуждаются и т.д.) тоже можно найти очень много интересного.



Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version