Теперь давайте познакомим класс 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) {}
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'
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
Обратите внимание, что свойство класса<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")
;
Делая это, мы получаем некоторые вещи бесплатно:
- Derived автоматически наследует все методы Python Base (завернутые функции членов C++).
- ЕслиБаза полиморфна,<
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называет политикупозже.
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]](/img/note.png) |
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();
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
С хорошо известен обилием операторов. 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())
.def(int() + self)
.def(self - self)
.def(self - int())
.def(self += int())
.def(self -= other<int>())
.def(self < self);
Сниппет кода выше очень понятен и не нуждается в объяснении. Это практически то же самое, что и подписи операторов. Обратите внимание, что<self
>относится к объекту FilePos. Кроме того, не каждый класс<T
>, с которым вам, возможно, придется взаимодействовать в выражении оператора, может быть построен по умолчанию. Вы можете использовать<other<T>()
>вместо фактического<T
>экземпляра при написании «самовыражений».
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))
.def(pow(self, other<Rational>))
.def(abs(self))
.def(str(self))
;
Нужно ли говорить больше?
![[Note]](/img/note.png) |
Note |
Что это такое<operator<< >? Метод<str >требует, чтобы<operator<< >выполнял свою работу (т.е.<operator<< >используется методом, определенным<def(str(self)) >). |