Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
Разработка программного обеспечения

Exposing Classes

Boost , Boost.Python Tutorial , Boost.Python Tutorial

Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Теперь давайте познакомим класс C++ с Python.

Рассмотрим класс/структуру C++, который мы хотим представить Python:

struct World
{
    void set(std::string msg) { this->msg = msg; }
    std::string greet() { return msg; }
    std::string msg;
};

Мы можем показать это Python, написав соответствующий Boost. Python C++ Враппер:

#include <boost/python.hpp>
using namespace boost::python;
BOOST_PYTHON_MODULE(hello)
{
    class_<World>("World")
        .def("greet", &World::greet)
        .def("set", &World::set)
    ;
}

Здесь мы написали обертку класса C++, которая раскрывает функции<greet>и<set>. Теперь, построив наш модуль в виде общей библиотеки, мы можем использовать наш класс<World>в Python. Вот пример сессии Python:

>>> import hello
>>> planet = hello.World()
>>> planet.set('howdy')
>>> planet.greet()
'howdy'

В нашем предыдущем примере не было явных конструкторов. Поскольку<World>объявляется простой структурой, он имеет неявный конструктор по умолчанию. Повышаю. Python по умолчанию раскрывает конструктор по умолчанию, поэтому мы смогли написать

>>> planet = hello.World()

Мы можем завернуть класс с конструктором без по умолчанию. Давайте рассмотрим наш предыдущий пример:

struct World
{
    World(std::string msg): msg(msg) {} // added constructor
    void set(std::string msg) { this->msg = msg; }
    std::string greet() { return msg; }
    std::string msg;
};

На этот раз<World>не имеет конструктора по умолчанию; наш предыдущий код обертки не смог бы собраться, когда библиотека попыталась его разоблачить. Мы должны рассказать<class_<World>>о конструкторе, которого мы хотим разоблачить.

#include <boost/python.hpp>
using namespace boost::python;
BOOST_PYTHON_MODULE(hello)
{
    class_<World>("World", init<std::string>())
        .def("greet", &World::greet)
        .def("set", &World::set)
    ;
}

<init<std::string>()>разоблачает конструктор, принимающий<std::string>(в Python конструкторы пишутся как<"__init__">).

Мы можем предоставить дополнительные конструкторы, передав больше<init<...>>с<def()>функции члена. Скажем, например, у нас есть другой конструктор Мира, принимающий в два дубля:

class_<World>("World", init<std::string>())
    .def(init<double, double>())
    .def("greet", &World::greet)
    .def("set", &World::set)
;

С другой стороны, если мы вообще не хотим разоблачать каких-либо строителей, мы можем использовать<no_init>вместо этого:

class_<Abstract>("Abstract", no_init)

Это фактически добавляет<__init__>метод, который всегда поднимает Python RuntimeError исключение.

Члены данных также могут подвергаться воздействию Python, чтобы к ним можно было получить доступ в качестве атрибутов соответствующего класса Python. Каждый элемент данных, который мы хотим раскрыть, может рассматриваться кактолько для чтенияилидля чтения-записи. Рассмотрим этот класс<Var>:

struct Var
{
    Var(std::string name) : name(name), value() {}
    std::string const name;
    float value;
};

Наш класс C++<Var>и его члены данных могут быть подвержены воздействию Python:

class_<Var>("Var", init<std::string>())
    .def_readonly("name", &Var::name)
    .def_readwrite("value", &Var::value);

Затем, в Python, предположим, что мы поместили наш класс Var в пространство имен, как мы делали раньше:

>>> x = hello.Var('pi')
>>> x.value = 3.14
>>> print x.name, 'is around', x.value
pi is around 3.14

Обратите внимание, что<name>выставляетсятолько для чтения, а<value>выставляетсядля чтения.

>>> x.name = 'e' # can't change name
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: can't set attribute

В C++ классы с общедоступными данными обычно не одобряются. Хорошо спроектированные классы, которые используют инкапсуляцию, скрывают данные класса. Единственный способ получить доступ к данным класса — это функции доступа (getter/setter). Функции доступа раскрывают свойства класса. Вот пример:

struct Num
{
    Num();
    float get() const;
    void set(float value);
    ...
};

Однако в Python доступ к атрибутам хорош; он не обязательно нарушает инкапсуляцию, чтобы позволить пользователям обрабатывать атрибуты напрямую, потому что атрибуты могут быть просто другим синтаксисом для вызова метода. Завернуть наш<Num>класс с помощью Boost. Python:

class_<Num>("Num")
    .add_property("rovalue", &Num::get)
    .add_property("value", &Num::get, &Num::set);

И, наконец, в Python:

>>> x = Num()
>>> x.value = 3.14
>>> x.value, x.rovalue
(3.14, 3.14)
>>> x.rovalue = 2.17 # error!

Обратите внимание, что свойство класса<rovalue>выставляется кактолько для чтения, поскольку функция заставщика<rovalue>не передается в:

.add_property("rovalue", &Num::get)

В предыдущих примерах мы рассматривали классы, которые не являются полиморфными. Так бывает нечасто. Большую часть времени мы будем обертывать полиморфные классы и классовые иерархии, связанные по наследству. Нам часто приходится писать буст. Обертки Python для классов, полученных из абстрактных базовых классов.

Рассмотрим эту тривиальную структуру наследования:

struct Base { virtual ~Base(); };
struct Derived : Base {};

И набор функций C++, работающих на<Base>и<Derived>экземплярах объектов:

void b(Base*);
void d(Derived*);
Base* factory() { return new Derived; }

Мы видели, как можно обернуть базовый класс<Base>:

class_<Base>("Base")
    /*...*/
    ;

Теперь мы можем сообщить Буту. Python наследственной связи между<Derived>и его базовым классом<Base>. Таким образом:

class_<Derived, bases<Base> >("Derived")
    /*...*/
    ;

Делая это, мы получаем некоторые вещи бесплатно:

  1. Derived автоматически наследует все методы Python Base (завернутые функции членов C++).
  2. ЕслиБаза полиморфна,<Derived>объекты, которые были переданы Python через указатель или ссылку на<Base>, могут быть переданы там, где ожидается указатель или ссылка на<Derived>.

Теперь мы рассмотрим свободные функции C++<b>и<d>и<factory>:

def("b", b);
def("d", d);
def("factory", factory);

Обратите внимание, что свободная функция<factory>используется для создания новых экземпляров класса<Derived>. В таких случаях мы используем<return_value_policy<manage_new_object>>, чтобы инструктировать Python принять указатель на<Base>и удерживать экземпляр в новом объекте Python<Base>, пока объект Python не будет уничтожен. Мы увидим больше роста. Pythonназывает политикупозже.

// Tell Python to take ownership of factory's result
def("factory", factory,
    return_value_policy<manage_new_object>());

В этом разделе мы узнаем, как заставить функции вести себя полиморфно через виртуальные функции. Продолжая наш пример, давайте добавим виртуальную функцию в наш класс<Base>:

struct Base
{
    virtual ~Base() {}
    virtual int f() = 0;
};

Одна из целей Boost. Python должен быть минимально навязчивым в существующем дизайне C++. В принципе, должна быть возможность разоблачить интерфейс для библиотеки 3-й партии, не меняя его. Идеально ничего не добавлять в наш класс<Base>. Тем не менее, когда у вас есть виртуальная функция, которая будет переопределена в Python и названа полиморфноиз C++, нам нужно добавить некоторые строительные леса, чтобы все работало должным образом. Что мы будем делать, так это писать обертку класса, которая происходит от<Base>, которая ненавязчиво подключится к виртуальным функциям, чтобы можно было назвать оверрайд Python:

struct BaseWrap : Base, wrapper<Base>
{
    int f()
    {
        return this->get_override("f")();
    }
};

Заметьте также, что в дополнение к наследованию от<Base>мы также умножаем наследство<wrapper<Base>>(см.Wrapper). Шаблон<wrapper>облегчает работу классов обертывания, которые предназначены для переопределения в Python.

Перегруженная функция виртуального члена BaseWrap<f>фактически вызывает соответствующий метод объекта Python через<get_override>.

Наконец, разоблачение<Base>:

class_<BaseWrap, boost::noncopyable>("Base")
    .def("f", pure_virtual(&Base::f))
    ;

<pure_virtual>Усиление. Python<f>является чисто виртуальной функцией.

[Note] Note

Функция и методы членов

Python, как и многие объектно-ориентированные языки, использует терминметодов. Методы примерно соответствуют функциям C++

В предыдущем разделе мы видели, как классы с чистыми виртуальными функциями обернуты с помощью Boost. Обертка класса Python. Если мы хотим обернутьне— чисто виртуальные функции, механизм немного отличается.

Напомним, что в предыдущем разделемы завернули класс с чистой виртуальной функцией, которую затем реализовали в C++, или производные от него классы Python. Наш базовый класс:

struct Base
{
    virtual int f() = 0;
};

Виртуальная функция<f>. Если, однако, его членская функция<f>не была объявлена чисто виртуальной:

struct Base
{
    virtual ~Base() {}
    virtual int f() { return 0; }
};

Мы завернем его таким образом:

struct BaseWrap : Base, wrapper<Base>
{
    int f()
    {
        if (override f = this->get_override("f"))
            return f(); // *note*
        return Base::f();
    }
    int default_f() { return this->Base::f(); }
};

Обратите внимание на то, как мы работаем<BaseWrap::f>. Теперь мы должны проверить, есть ли оверрайд<f>. Если нет, то мы призываем<Base::f()>.

Наконец, разоблачение:

class_<BaseWrap, boost::noncopyable>("Base")
    .def("f", &Base::f, &BaseWrap::default_f)
    ;

Обратите внимание, что мы разоблачаем как<&Base::f>, так и<&BaseWrap::default_f>. Повышаю. Python должен отслеживать 1) диспетчерскую функцию<f>и 2) функцию пересылки к своей реализации по умолчанию<default_f>. Для этой цели существует специальная функция<def>.

В Python результаты будут такими же, как и ожидалось:

>>> base = Base()
>>> class Derived(Base):
...     def f(self):
...         return 42
...
>>> derived = Derived()

Призыв<base.f()>:

>>> base.f()
0

Призыв<derived.f()>:

>>> derived.f()
42

Python Operators

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

Рассмотрим класс позиции файла<FilePos>и набор операторов, которые принимают экземпляры FilePos:

class FilePos { /*...*/ };
FilePos     operator+(FilePos, int);
FilePos     operator+(int, FilePos);
int         operator-(FilePos, FilePos);
FilePos     operator-(FilePos, int);
FilePos&    operator+=(FilePos&, int);
FilePos&    operator-=(FilePos&, int);
bool        operator<(FilePos, FilePos);

Класс и различные операторы могут быть отображены на Python довольно легко и интуитивно:

class_<FilePos>("FilePos")
    .def(self + int())          // __add__
    .def(int() + self)          // __radd__
    .def(self - self)           // __sub__
    .def(self - int())          // __sub__
    .def(self += int())         // __iadd__
    .def(self -= other<int>())
    .def(self < self);          // __lt__

Сниппет кода выше очень понятен и не нуждается в объяснении. Это практически то же самое, что и подписи операторов. Обратите внимание, что<self>относится к объекту FilePos. Кроме того, не каждый класс<T>, с которым вам, возможно, придется взаимодействовать в выражении оператора, может быть построен по умолчанию. Вы можете использовать<other<T>()>вместо фактического<T>экземпляра при написании «самовыражений».

Special Methods

Python имеет еще несколькоспециальных методов. Повышаю. Python поддерживает все стандартные имена специальных методов, поддерживаемые реальными экземплярами классов Python. Аналогичный набор интуитивно понятных интерфейсов можно также использовать для обертывания функций C++, которые соответствуют этим специальным функциям Python. Пример:

class Rational
{ public: operator double() const; };
Rational pow(Rational, Rational);
Rational abs(Rational);
ostream& operator<<(ostream&,Rational);
class_<Rational>("Rational")
    .def(float_(self))                  // __float__
    .def(pow(self, other<Rational>))    // __pow__
    .def(abs(self))                     // __abs__
    .def(str(self))                     // __str__
    ;

Нужно ли говорить больше?

[Note] Note

Что это такое<operator<<>? Метод<str>требует, чтобы<operator<<>выполнял свою работу (т.е.<operator<<>используется методом, определенным<def(str(self))>).


PrevUpHomeNext

Статья Exposing Classes раздела Boost.Python Tutorial Boost.Python Tutorial может быть полезна для разработчиков на c++ и boost.




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



:: Главная :: Boost.Python Tutorial ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-08-30 11:47:00
2025-05-19 18:35:21/0.010470151901245/0