Форум сайта python.su
Мне нужно сделать обертку для библиотеки, которая в свою очередь использует другие разделяемые библиотеки. В этом случае надо будет что-то делать с теми, или мне достаточно будет сделать только модуль для нужной мне?
Офлайн
Все зависит только от оборачиваемого кода. Забудьте на минуту о Py++ и думайте только о классах/функциях. Так, если в параметрах методов или базовых классах есть те, что лежат “снаружи” вашей библиотеки - прийдется сделать обертки и для них. Если вы “неудобный” метод не собираетесь использовать из питона - просто его не заворачивайте, и ничего делать не нужно (Py++ можно накормить списками исключений).
Офлайн
А Pyste еще можно пользоваться или он уже неработоспособен?
Офлайн
Не знаю насчет работоспособности - наверное все еще гож.
Py++ намного лучше.
А вообще - это всего лишь генераторы кода. Облегчают жизнь, но не более.
Настоятельно рекомендую разобраться с самим boost.python во избежание недоразумений - тогда и с генераторами проблем не будет.
Офлайн
Андрей СветловСпасибо за дельный совет, похоже, я и правда пытаюсь прыгнуть через ступень.
Настоятельно рекомендую разобраться с самим boost.python во избежание недоразумений - тогда и с генераторами проблем не будет.
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);
Отредактировано (Ноя. 29, 2009 22:35:24)
Офлайн
Что вас в 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);
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();
};
};
Офлайн
А совсем без изменений кода не обойтись? Мне, просто, нужно будет поддерживать эту обертку для постоянно развивающегося кода на С++. Принуждать коллегу бустифицировать его код не хочется, значит придется самому поддерживать набор патчей, что тоже не совсем приятно.
Первый способ выглядит заметно привлекательнее, во втором больно много переделок. Если в первом изменения касаются только объявления, но не реализации, и они достаточно однообразны, то можно ли автоматизировать этот процесс, например, Py++ такого не умеет? Или там будут встречаться нетривиальные ситуации?
—
А в чем ошибка в моем варианте оборачивания тех двух методов? Я проверил вот по такому коду в питоне, висячей ссылки не получается:
import World
npc = World.NPC()
npc.Id = 10
item = World.Item()
item.setOwner(npc)
del npc
print item.getOwner().Id
Отредактировано (Ноя. 30, 2009 14:22:45)
Офлайн
Может, в вашем случае можно код не менять - я же его не видел. Остается только гадать по облакам.
Автоматом - не выйдет. Даже в тривиальном примере потребовался weak_ptr - а будут случаи и посложнее.
Проще с коллегами разобраться :) - или найти общий язык/взаимопонимание.
Смотрите сами - я лишь попробовал указать на возможные скользкие места.
Офлайн
Кажется, я разобрался по большей части. После того, как поработал с простым 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
Офлайн
Правильной дорогой идете, товарищ.
От простого к сложному - и только так.
Я имею изрядный опыт возни с 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 (отклонены, направлены на доработку, обсуждаются и т.д.) тоже можно найти очень много интересного.
Офлайн