Evgenii Legotckoi
Evgenii LegotckoiJuly 18, 2018, 3:14 a.m.

Write your own Python bindings

Today we'll take a look at how you can create bindings for your own project.

The Qt Company is pleased to announce that Qt for Python will also include Shiboken, your primary binding tool.

Read the material below and you will get an idea of how to create Python bindings for a simple C++ library.

Hopefully this encourages you to do the same with your own custom libraries.

As with any Qt project, we are happy to provide articles on Shiboken, thereby improving its understanding for everyone.

Sample library

The main purposes of this post will be our use of a slightly nonsensical custom library called Universe. It provides two classes: Icecream (ice cream) and Truck (truck).


Icecreams is characterized by taste and the Truck serves as a delivery vehicle for Icecream to children in the neighborhood. Pretty simple.

We would like to use these classes inside Python. The use case is to add additional ice cream flavors or to check if the ice cream delivery was successful. In simple terms, we want to provide Python bindings for Icecream and Truck so that we can use them in our own Python script.

We'll skip some parts for brevity, but you can check out the repository for the full source code. Link to the repository pyside-setup/examples/samplebinding .

C++ library

First, let's take a look at the Icecream header file

class Icecream
{
public:
    Icecream(const std::string &flavor);
    virtual Icecream *clone();
    virtual ~Icecream();
    virtual const std::string getFlavor();

private:
    std::string m_flavor;
};

and header file 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;
};

Most APIs should be fairly easy to understand, but we'll summarize the important parts:

  • Icecream is a polymorphic type and is meant to be overridden
  • getFlavor() will return the flavor depending on the actual derived type
  • Truck saves a vector of Icecream objects it owns, which can be added via addIcecreamFlavor()
  • Truck arrival message can be configured with setArrivalMessage()
  • deliver() will tell us if the "ice cream" delivery was successful or not

Shiboken type system

To inform shiboken about the API, we need bindings, for this we make a header file that includes the types we are interested in:

#ifndef BINDINGS_H
#define BINDINGS_H
#include "icecream.h"
#include "truck.h"
#endif // BINDINGS_H

In addition, shiboken also requires an XML typesystem file that defines the relationship between C++ and Python types:

<?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>

The first important thing to notice is that we declare "bool" and "std::string" as primitive types. Some of the C++ methods use them as parameter/return types and so the shiboken needs to know about them. It can then generate the appropriate conversion code between C++ and Python.

Most C++ primitive types are handled by shiboken without requiring any additional code. We then declare the above two classes. One of them as "object-type" (object-type), and the other as "value-type" (value-object).

The main difference is that "object-type" are passed in the generated code as pointers, while "value-type" are copied (value semantics).

By setting the class names in the type system file, shiboken will automatically attempt to create bindings for all methods declared in the classes, so there is no need to manually specify all method names...

If you don't want to change the feature in any way, then that brings us to the next topic, Ownership Rules.

Shiboken cannot magically find out who is responsible for freeing C++ objects placed in Python code.

There can be many cases: Python must deallocate C++ memory when the number of references to a Python object becomes zero, or Python must never delete a C++ object, assuming it will be deleted at some point inside the C++ library. Or perhaps it's the parent of another object (like QWidgets, for example). In our case, the clone() method is only called inside the C++ library, and we assume that the C++ code takes care of freeing the memory of the cloned object.

As far as addIcecreamFlavor() is concerned, we know that the Truck resides in the Icecream object and will be removed immediately after the Truck is destroyed. So again, ownership is set to “c++.”

If we didn't specify ownership rules, then C++ objects would be deleted when the corresponding Python names went out of scope.

Assembly

To build a custom Universe library and then generate bindings for it, we provide a well-documented, mostly generic CMakeLists.txt file that you can reuse for your own libraries.

It basically boils down to calling "cmake." to set up the project and then building with the tool chain of your choice (we recommend the "(N) Makefiles" generator).

As a result of creating a project, you get two shared libraries: libuniverse. (so/dylib/dll) and Universe. (so/pyd).

The first is a custom C++ library and the last is a Python module that can be imported from a Python script (document, test driver).

Of course, there are also intermediate files created by shiboken (.h/.cpp files created to create Python bindings). Don't worry about them if you don't need to troubleshoot or if for some reason it fails to compile or doesn't behave as it should. Then you can submit a bug report to Qt Company!

And finally, we get to the Python part.

Using the Python module

In the next little script, we will use the Universe module, inherit from Icecream, implement virtual methods, instantiate objects, and much more.

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()

After importing the classes from our module, we create two derived (secondary) Icecream types that have been customized with flavors.

Then we create a truck (truck), add to it some regular variants (varieties) of Icecreams (ice cream) and two special ones.

We try to send (deliver) Icecream (ice cream).

If the delivery fails, we create a new truck with the old options copied over and a new magic option that will surely satisfy all customers.

The above script briefly shows the use of C++ type inference, overriding virtual methods, creating and destroying objects, etc.

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

Do you like it? Share on social networks!

Comments

Only authorized users can post comments.
Please, Log in or Sign up
г
  • ги
  • April 23, 2024, 12:51 p.m.

C++ - Test 005. Structures and Classes

  • Result:41points,
  • Rating points-8
l
  • laei
  • April 23, 2024, 6:19 a.m.

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:10points,
  • Rating points-10
l
  • laei
  • April 23, 2024, 6:17 a.m.

C++ - Тест 003. Условия и циклы

  • Result:50points,
  • Rating points-4
Last comments
k
kmssrFeb. 8, 2024, 3:43 p.m.
Qt Linux - Lesson 001. Autorun Qt application under Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lesson 007. Working with ICMP Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVADec. 25, 2023, 7:30 a.m.
Boost - static linking in CMake project under Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJoDec. 25, 2023, 5:38 a.m.
Boost - static linking in CMake project under Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
GvozdikDec. 18, 2023, 6:01 p.m.
Qt/C++ - Lesson 056. Connecting the Boost library in Qt for MinGW and MSVC compilers Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Now discuss on the forum
G
GarApril 22, 2024, 2:46 a.m.
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil AcademicsApril 20, 2024, 4:45 a.m.
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasovApril 14, 2024, 3:41 a.m.
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел ДорофеевApril 13, 2024, 11:35 p.m.
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrexApril 4, 2024, 1:47 a.m.
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

Follow us in social networks