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
AD

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

  • Result:50points,
  • Rating points-4
m

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

  • Result:80points,
  • Rating points4
m

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

  • Result:20points,
  • Rating points-10
Last comments
ИМ
Игорь МаксимовNov. 23, 2024, 12:51 a.m.
Django - Tutorial 017. Customize the login page to Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii LegotckoiNov. 1, 2024, 2:37 a.m.
Django - Lesson 064. How to write a Python Markdown extension Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZEOct. 19, 2024, 8:19 p.m.
Fb3 file reader on Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь МаксимовOct. 5, 2024, 7:51 p.m.
Django - Lesson 064. How to write a Python Markdown extension Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas5July 5, 2024, 11:02 p.m.
QML - Lesson 016. SQLite database and the working with it in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Now discuss on the forum
Evgenii Legotckoi
Evgenii LegotckoiJune 25, 2024, 3:11 a.m.
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey1Nov. 15, 2024, 7:04 p.m.
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProjectJune 4, 2022, 3:49 p.m.
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9AnonimOct. 25, 2024, 9:10 p.m.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Follow us in social networks