Сьогодні ми поглянемо, як ви можете створювати прив'язки до власного проекту.
У Qt Company раді повідомити, що Qt для Python також включатиме Shiboken - ваш основний інструмент створення прив'язки.
Прочитайте матеріал нижче і ви отримаєте уявлення про те, як створювати прив'язки Python для простої бібліотеки C++.
Будемо сподіватися, це сприяє тому, що ви зробите теж саме з власними бібліотеками користувача.
Як і в будь-якому проекті Qt ми раді представити статті щодо Shiboken, тим самим покращуючи його розуміння для всіх.
Приклад бібліотеки
Основними цілями цього посту буде використання нами трохи безглуздої бібліотеки користувача під назвою Universe. Вона передбачає два класи: Icecream (морозиво) та Truck (вантажівка).
Icecreams характеризується смаком, а Truck служить як засіб доставки Icecream для дітей в околиці. Досить просто.
Ми хотіли б використовувати ці класи всередині Python. Варіант використання – додавання додаткових уподобань морозива або перевірка успішності доставки морозива. Говорячи простими словами, ми хочемо забезпечити Python прив'язки для Icecream і Truck, щоб ми могли використовувати їх у власному скрипті Python.
Ми пропустимо деяку частину для стислості, але ви можете переглянути у репозиторії повний вихідний код. Посилання на репозиторій pyside-setup/examples/samplebinding
C++ бібліотека
По-перше, давайте поглянемо на заголовний файл Icecream
class Icecream { public: Icecream(const std::string &flavor); virtual Icecream *clone(); virtual ~Icecream(); virtual const std::string getFlavor(); private: std::string m_flavor; };
та заголовковий файл Truck
class Truck { public: Truck(bool leaveOnDestruction = false); Truck(const Truck &other); Truck& operator=(const Truck &other); ~Truck(); void addIcecreamFlavor(Icecream *icecream); void printAvailableFlavors() const; bool deliver() const; void arrive() const; void leave() const; void setLeaveOnDestruction(bool value); void setArrivalMessage(const std::string &message); private: void clearFlavors(); bool m_leaveOnDestruction = false; std::string m_arrivalMessage = "A new icecream truck has arrived!\n"; std::vector m_flavors; };
Більшість API повинні бути досить прості для розуміння, але ми підіб'ємо підсумки найважливіших частин:
- Icecream є поліморфним типом і призначається для перевизначення
- getFlavor() поверне смак залежно від фактичного похідного типу
- Truck зберігає вектор об'єктів Icecream, що належать йому, які можуть бути додані через addIcecreamFlavor()
- Повідомлення прибуття «вантажівки» можна налаштувати за допомогою setArrivalMessage()
- deliver() скаже нам, була доставка «морозива» успішною чи ні
Система типів Shiboken
Щоб інформувати shiboken про API, ми потребуємо прив'язки, для цього ми робимо файл-заголовок, який включає в себе типи, що нас цікавлять:
#ifndef BINDINGS_H #define BINDINGS_H #include "icecream.h" #include "truck.h" #endif // BINDINGS_H
Крім того, shiboken також потребує XML typesystem файл, який визначає відношення між типами C++ і Python:
<?xml version="1.0"?> <typesystem package="Universe"> <primitive-type name="bool"/> <primitive-type name="std::string"/> <object-type name="Icecream"> <modify-function signature="clone()"> <modify-argument index="0"> <define-ownership owner="c++"/> </modify-argument> </modify-function> </object-type> <value-type name="Truck"> <modify-function signature="addIcecreamFlavor(Icecream*)"> <modify-argument index="1"> <define-ownership owner="c++"/> </modify-argument> </modify-function> </value-type> </typesystem>
Перша важлива річ, на яку потрібно звернути увагу – те, що ми оголошуємо "bool" та "std::string", як примітивні типи. Деякі з методів C++ використовують їх як параметр / типи даних, що повертаються, і тому shiboken повинен знати про них. Потім він може генерувати відповідний код перетворення між C++ та Python.
Більшість примітивних типів C++ обробляються shiboken, не потребуючи додаткового коду. Потім ми оголошуємо два вищезгадані класи. Один із них як “object-type”(об'єкт-тип), а інший як “value-type” (значення-об'єкт).
Основна відмінність у тому, що “object-type” передаються у згенерованому коді, як покажчики, тоді як “value-type” копіюються (семантика значень).
Задавши імена класів у файлі системи типів, shiboken автоматично спробує створити прив'язки для всіх методів, оголошених у класах, тому немає необхідності вказувати всі імена методів вручну.
Якщо ви не хочете будь-яким чином змінити функцію, це призводить до наступної теми «Правила володіння».
Shiboken не може чарівним чином дізнатися, хто відповідає за звільнення об'єктів C++, розміщених у коді Python'а.
Може бути безліч випадків: Python повинен звільнити пам'ять C++, коли кількість посилань на об'єкт Python стає нульовим або Python ніколи не повинен видаляти об'єкт C++, допускаючи, що він буде видалений в певний момент всередині бібліотеки C++. Або, можливо, це батьківський елемент іншого об'єкта (наприклад, як у QWidgets). У разі метод clone() викликається лише усередині бібліотеки C++, і ми припускаємо, що код C++ подбає про звільнення пам'яті клонованого об'єкта.
Що стосується addIcecreamFlavor(), ми знаємо, що Truck розташовується в об'єкті Icecream, і буде видалено відразу після знищення Truck. Отже, знову, право володіння встановлюється на “c++.”
Якби ми не вказали правила володіння, то в цьому випадку об'єкти C++ будуть видалені, коли відповідні імена Python вийдуть за межі зони видимості.
Складання
Щоб зібрати користувальницьку бібліотеку Universe і потім згенерувати для неї прив'язки, ми надаємо добре документований, в основному загальний файл CMakeLists.txt, який ви можете повторно використати для власних бібліотек.
В основному це зводиться до виклику "cmake.", щоб налаштувати проект, а потім побудувати за допомогою ланцюжка інструментів на ваш вибір (ми рекомендуємо генератор "(N) Makefiles").
В результаті створення проекту ви отримуєте дві спільні бібліотеки: libuniverse. (so/dylib/dll) та Universe. (so/pyd).
Перша – це бібліотека C++ і остання – це модуль Python, який може бути імпортований зі скрипта (документ, тестовий драйвер) Python.
Звичайно, є також проміжні файли, створені для shiboken (файли .h / .cpp, створені для створення прив'язок Python). Не хвилюйтеся про них, якщо вам не потрібно усувати проблеми або з якоїсь причини не вдається скомпілювати або не поводиться так, як має бути. Тоді ви можете надіслати Qt Company звіт про помилку!
І, нарешті, ми дістаємось до частини Python.
Використання Python модуля
У наступному маленькому скрипті ми використовуватимемо модуль Universe, успадковуємося від Icecream, впровадимо віртуальні методи, інстанцуємо об'єкти та багато іншого
from Universe import Icecream, Truck class VanillaChocolateIcecream(Icecream): def __init__(self, flavor=""): super(VanillaChocolateIcecream, self).__init__(flavor) def clone(self): return VanillaChocolateIcecream(self.getFlavor()) def getFlavor(self): return "vanilla sprinked with chocolate" class VanillaChocolateCherryIcecream(VanillaChocolateIcecream): def __init__(self, flavor=""): super(VanillaChocolateIcecream, self).__init__(flavor) def clone(self): return VanillaChocolateCherryIcecream(self.getFlavor()) def getFlavor(self): base_flavor = super(VanillaChocolateCherryIcecream, self).getFlavor() return base_flavor + " and a cherry" if __name__ == '__main__': leave_on_destruction = True truck = Truck(leave_on_destruction) flavors = ["vanilla", "chocolate", "strawberry"] for f in flavors: icecream = Icecream(f) truck.addIcecreamFlavor(icecream) truck.addIcecreamFlavor(VanillaChocolateIcecream()) truck.addIcecreamFlavor(VanillaChocolateCherryIcecream()) truck.arrive() truck.printAvailableFlavors() result = truck.deliver() if result: print("All the kids got some icecream!") else: print("Aww, someone didn't get the flavor they wanted...") if not result: special_truck = Truck(truck) del truck print("") special_truck.setArrivalMessage("A new SPECIAL icecream truck has arrived!\n") special_truck.arrive() special_truck.addIcecreamFlavor(Icecream("SPECIAL *magical* icecream")) special_truck.printAvailableFlavors() special_truck.deliver() print("Now everyone got the flavor they wanted!") special_truck.leave()
Після імпортування класів з нашого модуля, ми створюємо два похідні (вторинні) типи Icecream, яким налаштували “смаки”.
Потім ми створюємо truck (вантажівка), додаємо до нього деякі звичайні варіанти (різновиди) Icecreams (морозива) та два спеціальні.
Ми намагаємося відправити (доставити) Icecream (морозиво).
Якщо ж доставка не вдалася, ми створюємо новий truck (вантажівка) зі старими варіантами, скопійованими, і новий чарівний варіант, який, безсумнівно, задовольнить усіх клієнтів.
У наведеному вище скрипті коротко показано використання висновків із типів C++, перевизначення віртуальних методів, створення та знищення об'єктів тощо.