Erstellen von Python-Bindungen fĂĽr C / C ++ - Bibliotheken mit SIP. Teil 1

Während der Arbeit an einem Projekt in Python besteht manchmal der Wunsch, eine Bibliothek zu verwenden, die nicht in Python geschrieben ist, sondern beispielsweise in C oder C ++. Die Gründe dafür mögen unterschiedlich sein. Erstens ist Python eine wunderbare Sprache, aber in einigen Situationen ist sie nicht schnell genug. Wenn Sie feststellen, dass die Leistung durch die Funktionen der Python-Sprache eingeschränkt ist, ist es sinnvoll, einen Teil des Programms in einer anderen Sprache zu schreiben (in diesem Artikel werden wir über C und C ++ sprechen), diesen Teil des Programms als Bibliothek anzuordnen und Python-Bindungen (Python-Bindungen) zu erstellen. darüber und verwenden Sie das so erhaltene Modul als normale Python-Bibliothek. Zweitens tritt eine Situation häufig auf, wenn Sie wissen, dass es eine Bibliothek gibt, die das erforderliche Problem löst. Leider ist diese Bibliothek nicht in Python geschrieben, sondern in demselben C oder C ++.In diesem Fall können wir auch eine Python-Bindung über die Bibliothek erstellen und verwenden, ohne daran zu denken, dass die Bibliothek ursprünglich nicht in Python geschrieben wurde.

Es gibt verschiedene Tools zum Erstellen von Python-Bindungen, von untergeordneten wie der Python / C-API bis zu ĂĽbergeordneten wie SWIG und SIP .

Ich hatte nicht das Ziel, verschiedene Arten der Erstellung von Python-Bindungen zu vergleichen, aber ich möchte über die Grundlagen der Verwendung eines Tools sprechen, nämlich SIP . Ursprünglich wurde SIP entwickelt, um eine Bindung um die Qt-Bibliothek - PyQt - zu erstellen , und wird auch zum Entwickeln anderer großer Python-Bibliotheken verwendet, z. B. wxPython .

In diesem Artikel wird gcc als Compiler fĂĽr C und g ++ als C ++ - Compiler verwendet. Alle Beispiele wurden unter Arch Linux und Python 3.8 getestet. Um die Beispiele nicht zu komplizieren, wird das Thema Kompilierung fĂĽr verschiedene Betriebssysteme und die Verwendung verschiedener Compiler (z. B. Visual Studio) nicht in den Umfang dieses Artikels aufgenommen.

Sie können alle Beispiele für diesen Artikel aus dem Repository auf github herunterladen .
Das Repository mit SIP-Quellen befindet sich unter https://www.riverbankcomputing.com/hg/sip/ . Mercurial wird als Versionskontrollsystem fĂĽr SIP verwendet.

Erstellen einer Bindung ĂĽber eine Bibliothek in C.


Schreiben einer Bibliothek in C.


Dieses Beispiel befindet sich im Ordner pyfoo_c_01 in der Quelle, aber in diesem Artikel gehen wir davon aus, dass wir alles von Grund auf neu machen.

Beginnen wir mit einem einfachen Beispiel. Zuerst erstellen wir eine einfache C-Bibliothek, die wir dann ĂĽber ein Python-Skript ausfĂĽhren. Lassen Sie unsere Bibliothek die einzige Funktion sein

int foo(char*);

Dies nimmt einen String und gibt seine Länge multipliziert mit 2 zurück. Die

Header-Datei foo.h sieht beispielsweise folgendermaĂźen aus:

#ifndef FOO_LIB
#define FOO_LIB

int foo(char* str);

#endif

Und die Datei mit der Implementierung von foo.cpp :

#include <string.h>

#include "foo.h"

int foo(char* str) {
	return strlen(str) * 2;
}

Um die Funktionalität der Bibliothek zu testen, schreiben wir ein einfaches main.c- Programm :

#include <stdio.h>

#include "foo.h"

int main(int argc, char* argv[]) {
	char* str = "0123456789";
	printf("%d\n", foo(str));
}

Erstellen Sie aus GrĂĽnden der Genauigkeit ein Makefile :

CC=gcc
CFLAGS=-c
DIR_OUT=bin

all: main

main: main.o libfoo.a
	$(CC) $(DIR_OUT)/main.o -L$(DIR_OUT) -lfoo -o $(DIR_OUT)/main

main.o: makedir main.c
	$(CC) $(CFLAGS) main.c -o $(DIR_OUT)/main.o

libfoo.a: makedir foo.c
	$(CC) $(CFLAGS) foo.c -o $(DIR_OUT)/foo.o
	ar rcs $(DIR_OUT)/libfoo.a $(DIR_OUT)/foo.o

makedir:
	mkdir -p $(DIR_OUT)

clean:
	rm -rf $(DIR_OUT)/*

Lassen Sie alle Quellen der Bibliothek foo in einem Unterordner von foo im Quellordner liegen:

foo_c_01 /
└── foo
    ├── foo.c
    ├── foo.h
    ├── main.c
    └── Makefile


Wir gehen in den Ordner foo und kompilieren die Quellen mit dem Befehl

make

Während der Kompilierung wird der Text angezeigt.

mkdir -p bin
gcc -c main.c -o bin/main.o
gcc -c foo.c -o bin/foo.o
ar rcs bin/libfoo.a bin/foo.o
gcc bin/main.o -Lbin -lfoo -o bin/main

Das Ăśbersetzungsergebnis wird in der platziert werden bin Ordner innerhalb des foo Ordner :

foo_c_01 /
└── foo
    Bin── bin
    │ ├ foo.o.
    │ ├ libfoo.a
    │ ├ Haupt
    │ └ main.o
    ├── foo.c
    ├── foo.h
    ├── main.c
    └── Makefile


Wir haben eine Bibliothek für statische Verknüpfungen und ein Programm zusammengestellt, das sie unter dem Namen main verwendet . Nach dem Kompilieren können Sie überprüfen, ob das Hauptprogramm gestartet wird .

Lassen Sie uns eine Python-Bindung ĂĽber die foo-Bibliothek erstellen.

SIP-Grundlagen


Zuerst müssen Sie SIP installieren. Dies erfolgt standardmäßig wie bei allen anderen Bibliotheken, die pip verwenden:

pip install --user sip

Wenn Sie in einer virtuellen Umgebung arbeiten, sollte der Parameter --user, der angibt, dass die SIP-Bibliothek im Benutzerordner und nicht global im System installiert werden muss, natĂĽrlich nicht angegeben werden.

Was mĂĽssen wir tun, damit die foo-Bibliothek aus Python-Code aufgerufen werden kann? Sie mĂĽssen mindestens zwei Dateien erstellen: eine im TOML-Format und den Namen pyproject.toml , und die zweite ist eine Datei mit der Erweiterung .sip. Lassen Sie uns nacheinander mit jedem von ihnen umgehen.

Wir müssen uns auf die Struktur der Quelle einigen. Im Ordner pyfoo_c befindet sich der Ordner foo , der die Quelle für die Bibliothek enthält. Nach der Kompilierung wird der Ordner bin im Ordner foo erstellt, die alle kompilierten Dateien enthält. Später werden wir dem Benutzer die Möglichkeit hinzufügen, die Pfade zu den Header- und Objektdateien der Bibliothek über die Befehlszeile anzugeben.

Die fĂĽr SIP erforderlichen Dateien befinden sich im selben Ordner wie der foo- Ordner .

pyproject.toml


Die Datei pyproject.toml ist keine Erfindung von SIP-Entwicklern, sondern das Python-Projektbeschreibungsformat, das in PEP 517 „Ein vom Buildsystem unabhängiges Format für Quellbäume“ und in PEP 518 „Festlegen der Mindestanforderungen an das Buildsystem für Python-Projekte“ beschrieben ist . Dies ist eine TOML-Datei , die als fortgeschrittenere Version des INI-Formats betrachtet werden kann, in der die Parameter in Form von „Schlüssel = Wert“ gespeichert sind und die Parameter nicht nur in Abschnitten wie [foo] gespeichert werden können, die in TOML-Begriffen als Tabellen bezeichnet werden, sondern und in Unterabschnitten des Formulars [foo.bar.spam]. Parameter können nicht nur Zeichenfolgen, sondern auch Listen, Zahlen und Boolesche Werte enthalten.

Diese Datei soll alles beschreiben, was zum Erstellen eines Python-Pakets erforderlich ist und nicht unbedingt SIP verwendet. Wie wir jedoch etwas später sehen werden, reicht diese Datei in einigen Fällen nicht aus, und außerdem muss ein kleines Python-Skript erstellt werden. Aber lassen Sie uns über alles in Ordnung sprechen.

Eine vollständige Beschreibung aller möglichen Parameter der Datei pyproject.toml , die sich auf SIP beziehen, finden Sie auf der SIP-Dokumentationsseite . Erstellen

Sie in unserem Beispiel die Datei pyproject.toml auf derselben Ebene wie der Ordner foo :

foo_c_01 /
├── foo
│ ├ bin
│ │ ├── foo.o.
│ │ ├ libfoo.a
│ │ ├ Haupt
│ │ └── main.o
│ ├ foo.c
│ ├ foo.h
│ ├ main.c
│ └ Makefile
└── pyproject.toml


Der Inhalt von pyproject.toml lautet wie folgt:

[build-system]
requires = ["sip >=5, <6"]
build-backend = "sipbuild.api"

[tool.sip.metadata]
name = "pyfoo"
version = "0.1"
license = "MIT"

[tool.sip.bindings.pyfoo]
headers = ["foo.h"]
libraries = ["foo"]
include-dirs = ["foo"]
library-dirs = ["foo/bin"]

Der Abschnitt [Build-System] ("Tabelle" in TOML-Begriffen) ist Standard und wird in PEP 518 beschrieben . Es enthält zwei Parameter:


Weitere Parameter sind in den Abschnitten [tool.sip. *] Beschrieben .

Der Abschnitt [tool.sip.metadata] enthält allgemeine Informationen zum Paket: den Namen des zu erstellenden Pakets (unser Paket heißt pyfoo , aber verwechseln Sie diesen Namen nicht mit dem Namen des Moduls, das wir später in Python importieren werden), die Versionsnummer des Pakets (in unserem Fall) Versionsnummer "0.1") und Lizenz (zum Beispiel " MIT ").

Das aus Sicht der Baugruppe wichtigste ist in den [tool.sip.bindings] beschrieben. pyfoo ].

Notieren Sie den Paketnamen in der AbschnittsĂĽberschrift. Wir haben diesem Abschnitt zwei Parameter hinzugefĂĽgt:

  • Header - Eine Liste der Header-Dateien, die zur Verwendung der foo-Bibliothek benötigt werden.
  • Bibliotheken - Eine Liste von Objektdateien, die fĂĽr die statische VerknĂĽpfung zusammengestellt wurden.
  • include-dirs ist der Pfad, in dem neben den an den C-Compiler angehängten Header-Dateien nach weiteren Header-Dateien gesucht wird. In diesem Fall wird nach der Datei foo.h gesucht .
  • library-dirs ist der Pfad, in dem neben den an den C-Compiler angehängten Objektdateien nach weiteren Objektdateien gesucht werden kann . In diesem Fall ist dies der Ordner, in dem die kompilierte Bibliotheksdatei foo erstellt wird .

Also haben wir die erste notwendige Datei für SIP erstellt. Nun fahren wir mit der Erstellung der nächsten Datei fort, die den Inhalt des zukünftigen Python-Moduls beschreibt.

pyfoo.sip


Erstellen Sie die Datei pyfoo.sip im selben Ordner wie die Datei pyproject.toml :

foo_c_01 /
├── foo
│ ├ bin
│ │ ├── foo.o.
│ │ ├ libfoo.a
│ │ ├ Haupt
│ │ └── main.o
│ ├ foo.c
│ ├ foo.h
│ ├ main.c
│ └ Makefile
├── pyfoo.sip
└── pyproject.toml


Eine Datei mit der Erweiterung .sip beschreibt die Schnittstelle der Quellbibliothek, die in Python in ein Modul konvertiert wird. Diese Datei hat ein eigenes Format, das wir nun betrachten werden, und ähnelt der C / C ++ - Headerdatei mit zusätzlichem Markup, das SIP beim Erstellen eines Python-Moduls helfen soll.

In unserem Beispiel sollte diese Datei pyfoo.sip heißen , da wir zuvor in der Datei pyproject.toml die Datei [tool.sip.bindings erstellt haben. pyfoo]. Im allgemeinen Fall kann es mehrere solcher Partitionen geben, und dementsprechend müssen mehrere * .sip-Dateien vorhanden sein. Wenn wir jedoch mehrere SIP-Dateien haben, ist dies aus Sicht von SIP ein Sonderfall, den wir in diesem Artikel nicht berücksichtigen werden. Bitte beachten Sie, dass im Allgemeinen der Name der .sip-Datei (und dementsprechend der Name des Abschnitts) möglicherweise nicht mit dem Namen des Pakets übereinstimmt, der im Parameter name im Abschnitt [tool.sip.metadata] angegeben ist .

Betrachten Sie die Datei pyfoo.sip aus unserem Beispiel:

%Module(name=foo, language="C")

int foo(char*);

Zeilen, die mit dem Zeichen "%" beginnen, werden als Direktiven bezeichnet. Sie sollten SIP mitteilen, wie das Python-Modul richtig zusammengesetzt und gestaltet werden soll. Eine vollständige Liste der Anweisungen finden Sie auf dieser Dokumentationsseite . Einige Anweisungen haben zusätzliche Parameter. Parameter sind möglicherweise nicht erforderlich.

In diesem Beispiel verwenden wir zwei Anweisungen. In den folgenden Beispielen lernen wir einige andere Anweisungen kennen.

Die Datei pyfoo.sip beginnt mit der Direktive % Module (Name = foo, Sprache = "C") . Bitte beachten Sie, dass wir den Wert des ersten Parameters ( Name ) ohne AnfĂĽhrungszeichen und den Wert des zweiten Parameters ( Sprache) angegeben haben) mit AnfĂĽhrungszeichen, wie Zeichenfolgen in C / C ++. Dies ist eine Anforderung dieser Direktive, wie in der Dokumentation zur % Module- Direktive beschrieben .

In der Direktive % Module ist nur der Parameter name erforderlich , der den Namen des Python-Moduls festlegt, aus dem die Bibliotheksfunktion importiert wird. In diesem Fall heißt das Modul foo und enthält die Funktion foo . Nach der Montage und Installation importieren wir es mit dem folgenden Code:

from foo import foo

Wir könnten dieses Modul in einem anderen Modul verschachteln, indem wir diese Zeile beispielsweise durch Folgendes ersetzen:

%Module(name=foo.bar, language="C")
...

Dann mĂĽsste die Funktion foo wie folgt importiert werden:

from foo.bar import foo

Der Sprachparameter der % Module- Direktive gibt die Sprache an, in der die Quellbibliothek geschrieben ist. Der Wert dieses Parameters kann entweder "C" oder "C ++" sein. Wenn dieser Parameter nicht angegeben wird, geht SIP davon aus, dass die Bibliothek in C ++ geschrieben ist.

Schauen Sie sich nun die letzte Zeile der Datei pyfoo.sip an :

int foo(char*);

Dies ist eine Beschreibung der Schnittstelle der Funktion aus der Bibliothek, die wir in das Python-Modul einfügen möchten. Basierend auf dieser Deklaration erstellt sip eine Python-Funktion. Ich denke, dass hier alles klar sein sollte.

Wir sammeln und prĂĽfen


Jetzt ist alles bereit, um ein Python-Paket mit einer Bindung für eine C-Bibliothek zu erstellen. Zunächst müssen Sie die Bibliothek selbst erstellen. Gehen Sie zum Ordner pyfoo_c_01 / foo / und starten Sie den Build mit dem Befehl make :

$ make

mkdir -p bin
gcc -c main.c -o bin/main.o
gcc -c foo.c -o bin/foo.o
ar rcs bin/libfoo.a bin/foo.o
gcc bin/main.o -Lbin -lfoo -o bin/main

Wenn alles gut gegangen ist, wird der Ordner bin im Ordner foo erstellt , in dem sich unter anderem eine kompilierte libfoo.a- Bibliothek befindet . Ich möchte Sie daran erinnern, dass wir hier, um nicht vom Hauptthema abgelenkt zu werden, nur über das Erstellen unter Linux mit gcc sprechen. Gehen Sie zurück zum Ordner pyfoo_c_01 . Jetzt ist es Zeit, die SIP-Teams kennenzulernen. Nach der Installation von SIP werden die folgenden Befehlszeilenbefehle verfügbar ( Dokumentationsseite ):



  • Schluck bauen . Erstellt eine Python-Erweiterungsobjektdatei.
  • sip-install . Erstellt eine Python-Erweiterungsobjektdatei und installiert sie.
  • sip-sdist. .tar.gz, pip.
  • sip-wheel. wheel ( .whl).
  • sip-module. , , SIP. , , . , standalone project, , , , .
  • sip-distinfo. .dist-info, wheel.

Diese Befehle mĂĽssen in dem Ordner ausgefĂĽhrt werden, in dem sich die Datei pyproject.toml befindet .

Um zu beginnen und die Funktionsweise von SIP besser zu verstehen, führen Sie den Befehl sip-build mit der Option --verbose aus, um eine detailliertere Ausgabe an die Konsole zu erhalten, und sehen Sie, was während des Erstellungsprozesses passiert.

$ sip-build --verbose

Diese Bindungen werden erstellt: pyfoo.
Generieren der Pyfoo-Bindungen ...
Kompilieren des 'foo'-Moduls ... Erstellen der
' foo'- Erweiterung
Erstellen
eines Builds Erstellen von build / temp.linux-x86_64-3.8
gcc -pthread -Wno-unbenutztes-Ergebnis -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c sipfoocmodule.c -o build / temp.linux-x86_64-3.8 / sipfoocmodule.o
sipfoocmodule.c: In der Funktion
func_foo : sipfoocmodule .c: 29: 22: Warnung: implizite Funktionsdeklaration “foo” [-Wimplicit-Funktionsdeklaration]
29 | sipRes = foo (a0);
| ^ ~~
gcc -pthread -Wno-unbenutztes-Ergebnis -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c array.c -o build / temp.linux-x86_64-3.8 / array.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c bool.cpp -o build / temp.linux-x86_64-3.8 / bool.o
gcc -pthread -Wno-unbenutztes-Ergebnis -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c objmap.c -o build / temp.linux-x86_64-3.8 / objmap.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c qtlib.c -o build / temp.linux-x86_64-3.8 / qtlib.o
gcc -pthread -Wno-unbenutztes-Ergebnis -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c int_convertors.c -o build / temp.linux-x86_64-3.8 / int_convertors.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c voidptr.c -o build / temp.linux-x86_64-3.8 / voidptr.o
gcc -pthread -Wno-unbenutztes-Ergebnis -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c apiversions.c -o build / temp.linux-x86_64-3.8 / apiversions.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c deskriptoren.c -o build / temp.linux-x86_64-3.8 / deskriptoren.o
gcc -pthread -Wno-unbenutztes-Ergebnis -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c threads.c -o build / temp.linux-x86_64-3.8 / threads.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -march = x86-64 -mtune = generisch -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c siplib.c -o build / temp.linux-x86_64-3.8 / siplib.o
siplib.c: In der Funktion "slot_richcompare":
siplib.c: 9536: 16: Warnung: "st" darf ohne Initialisierung in dieser Funktion [-Wmaybe-uninitialized]
9536 | verwendet werden slot = findSlotInClass (ctd, st);
| ^
~~~~~~~~~~~~~~~~~~~~~~~~~ siplib.c: 10671: 19: Anmerkung: “st” wurde hier deklariert
10671 | sipPySlotType st;
| ^ ~
siplib.c: In der Funktion "parsePass2":
siplib.c: 5625: 32: Warnung: "owner" kann ohne Initialisierung in dieser Funktion verwendet werden [-Wmaybe-uninitialized]
5625 | * owner = arg;
| ~~~~~~~~ ^ ~
g ++ -pthread -shared -Wl, -O1, - sort-common, - nach Bedarf, -z, relro, -z, jetzt -fno-semantic-interposition -Wl, -O1, - sort-common, - wie benötigt, -z, relro, -z, jetzt build / temp.linux-x86_64-3.8 / sipfoocmodule.o build / temp.linux-x86_64-3.8 / array.o build / temp.linux-x86_64-3.8 /bool.o build / temp.linux-x86_64-3.8 / objmap.o build / temp.linux-x86_64-3.8 / qtlib.o build / temp.linux-x86_64-3.8 / int_convertors.o build / temp.linux-x86_64 -3.8 / voidptr.o build / temp.linux-x86_64-3.8 / apiversions.o build / temp.linux-x86_64-3.8 / deskriptoren.o build / temp.linux-x86_64-3.8 / threads.o build / temp.linux -x86_64-3.8 / siplib.o -L ../../ foo / bin -L / usr / lib -lfoo -o / home / jenyay / projects / soft / sip-examples / pyfoo_c_01 / build / foo / foo. cpython-38-x86_64-linux-gnu.so
Das Projekt wurde erstellt.

Wir werden nicht tief in die Arbeit von SIP einsteigen, aber aus der Ausgabe geht hervor, dass einige Quellen kompilieren. Diese Quellen befinden sich im Ordner build / foo /, der mit diesem Befehl erstellt wurde :

pyfoo_c_01
├── bauen
│ └ foo
│ ├ apiversions.c
│ ├ array.c
│ ├ array.h
│ ├ bool.cpp
│ ├ bauen
│ │ └ temp.linux-x86_64-3.8
Ivers │ ├─ivers apiversions.o
│ │ ├ array.o
│ │ ├ bool.o
│ │ ├ DESkriptoren.o
│ │ ├ int_convertors.o
│ │ ├── objmap.o
│ │ ├ qtlib.o
│ │ ├── sipfoocmodule.o
│ │ ├── siplib.o
│ │ ├── threads.o
│ │ └ voidptr.o
│ ├ Deskriptoren.c
│ ├ foo.cpython-38-x86_64-linux-gnu.so
│ ├ int_convertors.c
│ ├ objmap.c
│ ├ qtlib.c
│ ├ sipAPIfoo.h
│ ├ sipfoocmodule.c
│ ├ sip.h
│ ├ sipint.h
│ ├ siplib.c
│ ├ threads.c
│ └ voidptr.c
├── foo
│ ├ bin
│ │ ├── foo.o.
│ │ ├ libfoo.a
│ │ ├ Haupt
│ │ └── main.o
│ ├ foo.c
│ ├ foo.h
│ ├ main.c
│ └ Makefile
├── pyfoo.sip
└── pyproject.toml


Zusätzliche Quellen wurden im Ordner build / foo angezeigt . Schauen wir uns aus Neugier die Datei sipfoocmodule.c an , da sie sich direkt auf das zu erstellende foo- Modul bezieht :

/*
 * Module code.
 *
 * Generated by SIP 5.1.1
 */

#include "sipAPIfoo.h"

/* Define the strings used by this module. */
const char sipStrings_foo[] = {
    'f', 'o', 'o', 0,
};

PyDoc_STRVAR(doc_foo, "foo(str) -> int");

static PyObject *func_foo(PyObject *sipSelf,PyObject *sipArgs)
{
    PyObject *sipParseErr = SIP_NULLPTR;

    {
        char* a0;

        if (sipParseArgs(&sipParseErr, sipArgs, "s", &a0))
        {
            int sipRes;

            sipRes = foo(a0);

            return PyLong_FromLong(sipRes);
        }
    }

    /* Raise an exception if the arguments couldn't be parsed. */
    sipNoFunction(sipParseErr, sipName_foo, doc_foo);

    return SIP_NULLPTR;
}

/* This defines this module. */
sipExportedModuleDef sipModuleAPI_foo = {
    0,
    SIP_ABI_MINOR_VERSION,
    sipNameNr_foo,
    0,
    sipStrings_foo,
    SIP_NULLPTR,
    SIP_NULLPTR,
    0,
    SIP_NULLPTR,
    SIP_NULLPTR,
    0,
    SIP_NULLPTR,
    0,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    {SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR,
            SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR},
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR
};

/* The SIP API and the APIs of any imported modules. */
const sipAPIDef *sipAPI_foo;

/* The Python module initialisation function. */
#if defined(SIP_STATIC_MODULE)
PyObject *PyInit_foo(void)
#else
PyMODINIT_FUNC PyInit_foo(void)
#endif
{
    static PyMethodDef sip_methods[] = {
        {sipName_foo, func_foo, METH_VARARGS, doc_foo},
        {SIP_NULLPTR, SIP_NULLPTR, 0, SIP_NULLPTR}
    };

    static PyModuleDef sip_module_def = {
        PyModuleDef_HEAD_INIT,
        "foo",
        SIP_NULLPTR,
        -1,
        sip_methods,
        SIP_NULLPTR,
        SIP_NULLPTR,
        SIP_NULLPTR,
        SIP_NULLPTR
    };

    PyObject *sipModule, *sipModuleDict;
    /* Initialise the module and get it's dictionary. */
    if ((sipModule = PyModule_Create(&sip_module_def)) == SIP_NULLPTR)
        return SIP_NULLPTR;

    sipModuleDict = PyModule_GetDict(sipModule);

    if ((sipAPI_foo = sip_init_library(sipModuleDict)) == SIP_NULLPTR)
        return SIP_NULLPTR;

    /* Export the module and publish it's API. */
    if (sipExportModule(&sipModuleAPI_foo, SIP_ABI_MAJOR_VERSION, SIP_ABI_MINOR_VERSION, 0) < 0)
    {
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }
    /* Initialise the module now all its dependencies have been set up. */
    if (sipInitModule(&sipModuleAPI_foo,sipModuleDict) < 0)
    {
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }

    return sipModule;
}

Wenn Sie mit der Python / C-API gearbeitet haben, werden Ihnen bekannte Funktionen angezeigt. Achten Sie besonders auf die Funktion func_foo ab Zeile 18.

Als Ergebnis der Kompilierung dieser Quellen wird die Datei build / foo / foo.cpython-38-x86_64-linux-gnu.so erstellt und enthält die Python-Erweiterung, die noch korrekt installiert werden muss.

Um die Erweiterung zu kompilieren und sofort zu installieren, können Sie den Befehl sip-install verwenden. Wir verwenden ihn jedoch nicht, da standardmäßig versucht wird, die erstellte Python-Erweiterung global im System zu installieren. Dieser Befehl hat den Parameter --target-dir, mit dem Sie den Pfad angeben können, unter dem Sie die Erweiterung installieren möchten. Wir verwenden jedoch besser andere Tools, die Pakete erstellen, die dann mit pip installiert werden können.

Verwenden Sie zuerst den Befehl sip-sdist . Die Verwendung ist sehr einfach:

$ sip-sdist

The sdist has been built.

Danach wird die Datei pyfoo-0.1.tar.gz erstellt , die mit dem folgenden Befehl installiert werden kann:

pip install --user pyfoo-0.1.tar.gz

Als Ergebnis werden die folgenden Informationen angezeigt und das Paket wird installiert:

Processing ./pyfoo-0.1.tar.gz
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
    Preparing wheel metadata ... done
Building wheels for collected packages: pyfoo
  Building wheel for pyfoo (PEP 517) ... done
  Created wheel for pyfoo: filename=pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl size=337289 sha256=762fc578...
  Stored in directory: /home/jenyay/.cache/pip/wheels/54/dc/d8/cc534fff...
Successfully built pyfoo
Installing collected packages: pyfoo
  Attempting uninstall: pyfoo
    Found existing installation: pyfoo 0.1
    Uninstalling pyfoo-0.1:
      Successfully uninstalled pyfoo-0.1
Successfully installed pyfoo-0.1

Stellen wir sicher, dass es uns gelungen ist, eine Python-Bindung herzustellen. Wir starten Python und versuchen, die Funktion aufzurufen. Ich möchte Sie daran erinnern, dass das pyfoo- Paket gemäß unseren Einstellungen das foo- Modul enthält , das die foo- Funktion hat .

>>> from foo import foo
>>> foo(b'123456')
12

Bitte beachten Sie, dass wir als Parameter für die Funktion nicht nur eine Zeichenfolge übergeben, sondern eine Zeichenfolge von Bytes b'123456 '- ein direktes Analogon von char * zu C. Wenig später werden wir die Konvertierung von char * zu str hinzufügen und umgekehrt. Das Ergebnis wurde erwartet. Ich möchte Sie daran erinnern, dass die Funktion foo die doppelte Größe eines Arrays vom Typ char * zurückgibt , das als Parameter übergeben wurde.

Versuchen wir, anstelle einer Liste von Bytes einen regulären Python-String an die foo- Funktion zu übergeben.

>>> from foo import foo
>>> foo('123456')

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo(str): argument 1 has unexpected type 'str'

Die erstellte Bindung konnte die Zeichenfolge nicht in char * konvertieren . Wir werden im nächsten Abschnitt erläutern, wie dies zu tun ist.

Herzlichen GlĂĽckwunsch, wir haben die erste Bindung fĂĽr eine in C geschriebene Bibliothek erstellt.

Beenden Sie den Python-Interpreter und setzen Sie die Baugruppe im Radformat zusammen. Wie Sie höchstwahrscheinlich wissen, ist Wheel ein relativ neues Paketformat, das in letzter Zeit allgemein verwendet wurde. Das Format ist in PEP 427, „Das Wheel Binary Package Format 1.0“ beschrieben, aber eine Beschreibung der Funktionen des Wheel-Formats ist ein Thema, das eines separaten großen Artikels würdig ist. Für uns ist es wichtig, dass der Benutzer das Paket mit pip einfach im Radformat installieren kann.

Ein Paket im Radformat ist nicht komplizierter als ein Paket im SDIST-Format. Dazu im Ordner mit der Dateipyproject.toml muss den Befehl ausfĂĽhren

sip-wheel

Nach dem Ausführen dieses Befehls wird der Erstellungsprozess angezeigt und möglicherweise werden vom Compiler Warnungen ausgegeben :

$ sip-Wheel

Diese Bindungen werden erstellt: pyfoo.
Generieren der Pyfoo-Bindungen ...
Kompilieren des 'foo'-Moduls ...
sipfoocmodule.c: In der Funktion func_foo:
sipfoocmodule.c: 29: 22: Warnung: implizite Deklaration der foo-Funktion [-Wimplicit-Funktionsdeklaration]
29 | sipRes = foo (a0);
| ^ ~~
siplib.c: In der Funktion "slot_richcompare":
siplib.c: 9536: 16: Warnung: "st" kann ohne Initialisierung in dieser Funktion verwendet werden [-Wmaybe-uninitialized]
9536 | slot = findSlotInClass (ctd, st);
| ^
~~~~~~~~~~~~~~~~~~~~~~~~~ siplib.c: 10671: 19: Bemerkung: „st“ wurde hier deklariert
10671 | sipPySlotType st;
| ^ ~
siplib.c: In der Funktion "parsePass2":
siplib.c: 5625: 32: Warnung: "owner" kann ohne Initialisierung in dieser Funktion verwendet werden [-Wmaybe-uninitialized]
5625 | * owner = arg;
| ~~~~~~~~ ^ ~ ~
Das Rad wurde gebaut.

Wenn die Assembly abgeschlossen ist (unser kleines Projekt wird schnell kompiliert), wird im Projektordner eine Datei mit dem Namen pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl oder ähnlichem angezeigt . Der Name der generierten Datei kann je nach Betriebssystem und Version von Python unterschiedlich sein.

Jetzt können wir dieses Paket mit pip installieren:

pip install --user --upgrade pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl

Die Option --upgrade wird hier verwendet , damit pip das zuvor installierte pyfoo-Modul ersetzt.

Ferner können das foo- Modul und das pyfoo- Paket wie oben gezeigt verwendet werden.

FĂĽgen Sie char * Konvertierungsregeln hinzu


Im vorherigen Abschnitt ist das Problem aufgetreten, dass die Funktion foo nur eine Reihe von Bytes, keine Zeichenfolgen akzeptieren kann. Jetzt werden wir dieses Manko beheben. Zu diesem Zweck verwenden wir ein anderes SIP-Tool - Anmerkungen . Anmerkungen werden in .sip-Dateien verwendet und auf einige Codeelemente angewendet: Funktionen, Klassen, Funktionsargumente, Ausnahmen, Variablen usw. Anmerkungen werden zwischen Schrägstrichen geschrieben: / annotation / .

Eine Annotation kann als Flag fungieren, das in dem Status gesetzt oder nicht gesetzt sein kann, zum Beispiel: / ReleaseGIL / , oder einigen Annotationen müssen einige Werte zugewiesen werden, zum Beispiel: / Encoding = "UTF-8" /. Wenn mehrere Anmerkungen auf ein Objekt angewendet werden müssen, werden sie durch Kommas in Schrägstrichen getrennt: / annotation_1, annotation_2 /. Fügen Sie

im folgenden Beispiel, das sich im Ordner pyfoo_c_02 befindet , eine Datei pyfoo.sip hinzu. Annotation parameter function foo :

%Module(name=foo, language="C")

int foo(char* /Encoding="UTF-8"/);

Die Annotation " Codierung" gibt an, in welcher Codierung die an die Funktion zu übergebende Zeichenfolge codiert werden soll. Die Werte für diese Anmerkung können sein: ASCII, Latin-1, UTF-8 oder None. Wenn die Annodation "Codierung" nicht angegeben oder gleich " Keine" ist , wird der Parameter für eine solche Funktion keiner Codierung unterzogen und unverändert an die Funktion übergeben. In diesem Fall muss der Parameter im Python-Code jedoch vom Typ " Bytes" sein , d. H. ein Array von Bytes, wie wir im vorherigen Beispiel gesehen haben. Wenn die Codierung angegeben ist, kann dieser Parameter eine Zeichenfolge sein (geben Sie str in Python ein). Kodieren Annotation kann nur auf die Parameter des Typs angewendet werden , char , const char ,char * oder const char * .

Lassen Sie uns überprüfen, wie die foo- Funktion des foo- Moduls jetzt funktioniert . Um dies zu tun, wie zuvor, müssen Sie zuerst die kompilieren foo - Bibliothek mit dem Aufruf des make - Befehl innerhalb des foo - Ordner , und dann rufen Sie den Befehl zum Beispiel sip-Rad, aus dem pyfoo_c_02 Beispiel Ordner . Die Datei pyfoo-0.2-cp38-cp38-manylinux1_x86_64.whl oder mit einem ähnlichen Namen wird erstellt , die mit dem folgenden Befehl festgelegt werden kann:

pip install --user --upgrade pyfoo-0.2-cp38-cp38-manylinux1_x86_64.whl

Wenn alles gut gegangen ist, starten Sie den Python-Interpreter und versuchen Sie, die Funktion foo mit einem String-Argument aufzurufen :

>>> from foo import foo

>>> foo(b'qwerty')
12

>>> foo('qwerty')
12

>>> foo('')
24

Zunächst stellen wir sicher, dass die Verwendung von Bytes weiterhin möglich ist. Danach stellen wir sicher, dass wir jetzt auch String-Argumente an die foo- Funktion übergeben können . Beachten Sie, dass die Funktion foo für ein Zeichenfolgenargument mit russischen Buchstaben einen doppelt so großen Wert zurückgab wie für eine Zeichenfolge, die nur lateinische Buchstaben enthält. Dies geschah aufgrund der Tatsache, dass die Funktion foo nicht die Zeichenfolgenlänge in Zeichen zählt (und verdoppelt), sondern die Länge des char * -Arrays . Da in UTF-8 die Codierung russischer Buchstaben 2 Bytes belegt, beträgt die Größe des char * -Arrays nach der Konvertierung von Python-Strings waren doppelt so lang.

Fein! Wir haben das Problem mit dem Funktionsargument gelöstfoo , aber was ist, wenn wir Dutzende oder Hunderte solcher Funktionen in unserer Bibliothek haben, müssen Sie die Parametercodierung für jede von ihnen angeben? Oft ist die in einem Programm verwendete Codierung dieselbe, und es gibt keinen Zweck für unterschiedliche Funktionen, unterschiedliche Codierungen anzuzeigen. In diesem Fall kann SIP die Standardcodierung angeben. Wenn für eine Funktion eine andere Codierung erforderlich ist, kann diese mithilfe der Codierungsanmerkung neu definiert werden .

Um die Codierung der Funktionsparameter standardmäßig festzulegen, wird die Direktive % DefaultEncoding verwendet . Die Verwendung wird im Beispiel im Ordner pyfoo_c_03 gezeigt . Ändern Sie die Datei pyfoo.sip , um die

Direktive % DefaultEncoding zu nutzen, jetzt ist sein Inhalt wie folgt:

%Module(name=foo, language="C")
%DefaultEncoding "UTF-8"

int foo(char*);

Wenn das Argument nun eine Funktion vom Typ char , char * usw. ist. Wenn keine Encoding- Annotation vorhanden ist , wird die Codierung der % DefaultEncoding- Direktive entnommen. Wenn dies nicht der Fall ist, wird die Konvertierung nicht durchgefĂĽhrt und fĂĽr alle Parameter char * usw. Es ist notwendig, keine Zeilen, sondern Bytes zu ĂĽbertragen .

Ein Beispiel aus dem pyfoo_c_03 Ordner wird in der gleichen Weise wie ein Beispiel aus dem gesammelten und ĂĽberprĂĽft pyfoo_c_02 Ordner .

Kurz ĂĽber project.py. Montage automatisieren


Bisher haben wir zwei Dienstprogrammdateien verwendet, um eine Python-Bindung zu erstellen - pyproject.toml und pyfoo.sip . Jetzt werden wir eine andere solche Datei kennenlernen, die project.py heißen sollte . Mit diesem Skript können wir den Erstellungsprozess unseres Pakets beeinflussen. Lassen Sie uns die Build-Automatisierung durchführen. Um die Beispiele pyfoo_c_01 - pyfoo_c_03 aus den vorherigen Abschnitten zu sammeln , mussten Sie zuerst zum Ordner foo / gehen , dort mit dem Befehl make kompilieren , zu dem Ordner zurückkehren, in dem sich die Datei pyproject.toml befindet , und erst dann mit einem der Pakete das Paket erstellen sip- * Befehle .

Unser Ziel ist es nun sicherzustellen, dass beim AusfĂĽhren der Befehle sip-build , sip-sdist und sip-Wheel zuerst die Assembly der foo C-Bibliothek gestartet wird und dann der Befehl selbst bereits gestartet wird.

Ein in diesem Abschnitt erstelltes Beispiel befindet sich im Quellordner pyfoo_c_04 .

Um den Erstellungsprozess zu ändern, können wir eine Klasse in der Datei project.py deklarieren (der Dateiname sollte genau das sein), die von der Klasse sipbuild.Project abgeleitet ist . Diese Klasse verfügt über Methoden, die wir selbst überschreiben können. Derzeit interessieren uns folgende Methoden:

  • bauen . Wird während des Aufrufs des Befehls sip-build aufgerufen .
  • build_sdist . Wird aufgerufen, wenn der Befehl sip-sdist aufgerufen wird .
  • build_wheel . Wird aufgerufen, wenn der Befehl sip-whe aufgerufen wird .
  • installieren . Wird aufgerufen, wenn der Befehl sip-install aufgerufen wird .

Das heißt, wir können das Verhalten dieser Befehle neu definieren. Genau genommen werden die aufgelisteten Methoden in der abstrakten Klasse sipbuild.AbstractProject deklariert , aus der die abgeleitete Klasse sipbuild.Project erstellt wird .

Erstellen Sie eine project.py- Datei mit folgendem Inhalt:

import os
import subprocess

from sipbuild import Project

class FooProject(Project):
    def _build_foo(self):
        cwd = os.path.abspath('foo')
        subprocess.run(['make'], cwd=cwd, capture_output=True, check=True)

    def build(self):
        self._build_foo()
        super().build()

    def build_sdist(self, sdist_directory):
        self._build_foo()
        return super().build_sdist(sdist_directory)

    def build_wheel(self, wheel_directory):
        self._build_foo()
        return super().build_wheel(wheel_directory)

    def install(self):
        self._build_foo()
        super().install()

Wir haben die FooProject- Klasse deklariert , die von der sipbuild.Project- Klasse abgeleitet ist, und die Methoden build , build_sdist , build_wheel und install darin definiert . Bei all diesen Methoden rufen wir die gleichnamigen Methoden aus der Basisklasse auf, bevor wir die Methode _build_foo aufrufen , mit der die AusfĂĽhrung des Befehls make im Ordner foo gestartet wird .

Beachten Sie, dass die Methoden build_sdist und build_wheel den Namen der von ihnen erstellten Datei zurĂĽckgeben sollten. Dies ist nicht in der Dokumentation geschrieben, sondern in den SIP-Quellen angegeben.

Jetzt mĂĽssen wir den Befehl make nicht mehr ausfĂĽhrenmanuell, um die foo Bibliothek zu erstellen , wird dies automatisch durchgefĂĽhrt.

Wenn Sie jetzt den Befehl sip- whe im Ordner pyfoo_c_04 ausführen , wird eine Datei mit dem Namen pyfoo-0.4-cp38-cp38-manylinux1_x86_64.whl oder ähnlichem erstellt, abhängig von Ihrem Betriebssystem und der Version von Python. Dieses Paket kann mit dem folgenden Befehl installiert werden:



pip install --user --upgrade pyfoo-0.4-cp38-cp38-manylinux1_x86_64.whl

Danach können Sie sicherstellen, dass die foo- Funktion des foo- Moduls weiterhin funktioniert.

FĂĽgen Sie zu erstellende Befehlszeilenoptionen hinzu


Das folgende Beispiel befindet sich im Ordner pyfoo_c_05 und das Paket hat eine Versionsnummer von 0,5 (siehe die Einstellungen in der Datei pyproject.toml ). Dieses Beispiel basiert auf einem Beispiel aus der Dokumentation mit einigen Korrekturen. In diesem Beispiel werden wir unsere Datei project.py wiederholen und neue Befehlszeilenoptionen fĂĽr den Build hinzufĂĽgen.

In unseren Beispielen bauen wir eine sehr einfache Bibliothek fooIn realen Projekten kann die Bibliothek sehr groß sein, und es ist dann nicht sinnvoll, sie in den Quellcode des Python-Bindungsprojekts aufzunehmen. Ich möchte Sie daran erinnern, dass SIP ursprünglich erstellt wurde, um eine Bindung für eine so große Bibliothek wie Qt zu erstellen. Sie können natürlich argumentieren, dass Submodule von git bei der Organisation der Quelle helfen können, aber das ist nicht der Punkt. Angenommen, die Bibliothek befindet sich möglicherweise nicht in dem Ordner mit der Bindungsquelle. In diesem Fall stellt sich die Frage, wo der SIP-Kollektor nach dem Bibliotheksheader und den Objektdateien suchen soll. In diesem Fall haben verschiedene Benutzer möglicherweise ihre eigenen Möglichkeiten, die Bibliothek zu platzieren.

Um dieses Problem zu lösen, fügen wir dem Build-System zwei neue Befehlszeilenoptionen hinzu, mit denen Sie den Pfad zur Datei foo.h (Parameter --foo-include-dir) angeben können) und zur Bibliotheksobjektdatei (Parameter --foo-library-dir ). Außerdem wird davon ausgegangen, dass sich die foo- Bibliothek zusammen mit den Bindungsquellen befindet , wenn diese Parameter nicht angegeben werden .

Wir mĂĽssen die Datei project.py erneut erstellen und darin eine von deipbuild.Project abgeleitete Klasse deklarieren . Schauen wir uns zuerst die neue Version der Datei project.py an und sehen dann , wie sie funktioniert.

import os

from sipbuild import Option, Project

class FooProject(Project):
    """        
       foo.
    """

    def get_options(self):
        """     . """

        tools = ['build', 'install', 'sdist', 'wheel']

        #   .
        options = super().get_options()

        #   
        inc_dir_option = Option('foo_include_dir',
                                help="the directory containing foo.h",
                                metavar="DIR",
                                default=os.path.abspath('foo'),
                                tools=tools)
        options.append(inc_dir_option)

        lib_dir_option = Option('foo_library_dir',
                                help="the directory containing the foo library",
                                metavar="DIR",
                                default=os.path.abspath('foo/bin'),
                                tools=tools)

        options.append(lib_dir_option)

        return options

    def apply_user_defaults(self, tool):
        """    . """

        #     
        super().apply_user_defaults(tool)

        #  ,         
        self.foo_include_dir = os.path.abspath(self.foo_include_dir)
        self.foo_library_dir = os.path.abspath(self.foo_library_dir)

    def update(self, tool):
        """   . """

        #   pyfoo
        # (  pyproject.toml  [tool.sip.bindings.pyfoo])
        foo_bindings = self.bindings['pyfoo']

        #   include_dirs  
        if self.foo_include_dir is not None:
            foo_bindings.include_dirs = [self.foo_include_dir]

        #   library_dirs  
        if self.foo_library_dir is not None:
            foo_bindings.library_dirs = [self.foo_library_dir]

        super().update(tool)

Wir haben erneut die FooProject- Klasse erstellt , die von sipbuild.Project abgeleitet ist . In diesem Beispiel ist die automatische Assemblierung der foo- Bibliothek deaktiviert , da jetzt davon ausgegangen wird, dass sie sich an einem anderen Ort befinden kann. Zum Zeitpunkt der Erstellung der Bindung sollten die Header- und Objektdateien bereit sein. In

der FooProject- Klasse werden drei Methoden neu definiert: get_options , apply_user_defaults und update . Betrachten Sie sie genauer.

Beginnen wir mit der Methode get_options . Diese Methode sollte eine Liste der Instanzen der Klasse sipbuild.Option zurĂĽckgeben. Jedes Listenelement ist eine Befehlszeilenoption. Innerhalb der ĂĽberschriebenen Methode erhalten wir die Liste der Standardoptionen (die Optionsvariable ), indem wir die gleichnamige Basisklassenmethode aufrufen, dann zwei neue Optionen ( --foo_include_dir und --foo_library_dir ) erstellen , sie der Liste hinzufĂĽgen und diese Liste dann von der Funktion zurĂĽckgeben.

Der Konstruktor der Option Klasse akzeptiert einen erforderlichen Parameter (Option Namen) und eine ausreichend groĂźe Anzahl von optionalen, dass die Art der Wert fĂĽr diesen Parameter, Standardwert, Parameterbeschreibung und einige andere beschreiben. In diesem Beispiel werden die folgenden Optionen fĂĽr die Option Konstruktor :

  • help , , sip-wheel -h
  • metavar — , , . metavar «DIR», , — .
  • default — . , , foo , ( ).
  • tools — , . sip-build, sip-install, sip-sdist sip-wheel, tools = ['build', 'install', 'sdist', 'wheel'].

Die folgende überladene Methode apply_user_defaults dient zum Festlegen von Parameterwerten, die der Benutzer über die Befehlszeile übergeben kann. Die Methode apply_user_defaults aus der Basisklasse erstellt für jeden in der Methode get_options erstellten Befehlszeilenparameter eine Variable (Klassenmitglied). Daher ist es wichtig, die gleichnamige Basisklassenmethode aufzurufen, bevor die erstellten Variablen verwendet werden, damit alle mit den Befehlszeilenparametern erstellten Variablen erstellt und mit Standardwerten initialisiert werden . Danach werden in unserem Beispiel die Variablen self.foo_include_dir und self.foo_library_dir erstellt. Wenn der Benutzer die entsprechenden Befehlszeilenparameter nicht angegeben hat, werden Standardwerte gemäß den Parametern des Konstruktors der Optionsklasse ( Standardparameter ) verwendet. Wenn der Standardparameter nicht festgelegt ist, wird er je nach Typ des erwarteten Parameterwerts entweder None oder eine leere Liste oder 0 initialisiert.

Innerhalb der Methode apply_user_defaults stellen wir sicher, dass die Pfade in den Variablen self.foo_include_dir und self.foo_library_dir immer absolut sind. Dies ist erforderlich, damit es nicht davon abhängt, wie der Arbeitsordner zum Zeitpunkt des Starts der Assembly lautet.

Die letzte ĂĽberladene Methode in dieser Klasse ist update.. Diese Methode wird aufgerufen, wenn die zuvor vorgenommenen Ă„nderungen auf das Projekt angewendet werden mĂĽssen. Ă„ndern oder fĂĽgen Sie beispielsweise die in der Datei pyproject.toml angegebenen Parameter hinzu . In den vorherigen Beispielen haben wir die Pfade zu den Header- und Objektdateien mithilfe der Parameter include-dirs und library-dirs im Abschnitt [tool.sip.bindings.pyfoo] festgelegt . Jetzt werden wir diese Parameter aus dem Skript project.py festlegen , sodass wir in der Datei pyproject.toml die folgenden Parameter entfernen:

[build-system]
requires = ["sip >=5, <6"]
build-backend = "sipbuild.api"

[tool.sip.metadata]
name = "pyfoo"
version = "0.3"
license = "MIT"

[tool.sip.bindings.pyfoo]
headers = ["foo.h"]
libraries = ["foo"]

Innerhalb der Methode aktualisieren Sie uns aus dem Wörterbuch self.bindings keyed pyfoo gat Instanz sipbuild.Bindings . Der Schlüsselname entspricht den [tool.sip.bindings. pyfoo ] aus der Datei pyproject.toml , und die so erhaltene Klasseninstanz beschreibt die in diesem Abschnitt beschriebenen Einstellungen. Dann werden den Mitgliedern dieser Klasse include_dirs und library_dirs (die Namen der Mitglieder entsprechen den Parametern include-dirs und library-dirs mit einem durch Unterstriche ersetzten Bindestrich) Listen zugewiesen, die die in den Mitgliedern self.foo_include_dir und self.foo_library_dir gespeicherten Pfade enthalten. In diesem Beispiel überprüfen wir aus Gründen der Genauigkeit, ob die Werte self.foo_include_dir und self.foo_library_dir nicht gleich None sind. In diesem Beispiel ist diese Bedingung jedoch immer erfüllt, da die von uns erstellten Befehlszeilenparameter Standardwerte haben.

Daher haben wir die Konfigurationsdateien so vorbereitet, dass während der Montage die Pfade zu den Header- und Objektdateien angegeben werden konnten. Lassen Sie uns überprüfen, was passiert ist.

Stellen Sie zunächst sicher, dass die Standardwerte funktionieren. Wechseln Sie dazu in den Ordner pyfoo_c_05 / foo und erstellen Sie die Bibliothek mit dem Befehl make , da wir in diesem Beispiel die automatische Bibliothekserstellung deaktiviert haben.

Gehen Sie danach zum Ordnerpyfoo_c_05 und führen Sie den Befehl sip- whe aus . Als Ergebnis dieses Befehls wird die Datei pyfoo-0.5-cp38-cp38-manylinux1_x86_64.whl oder mit einem ähnlichen Namen erstellt .

Verschieben Sie nun den Ordner foo irgendwo auĂźerhalb des Ordners pyfoo_c_05 und fĂĽhren Sie den Befehl sip- whe erneut aus . Als Ergebnis erhalten wir den erwarteten Fehler, dass wir keine Objektbibliotheksdatei haben:

usr/bin/ld:   -lfoo
collect2: :  ld     1
sip-wheel: Unable to compile the 'foo' module: command 'g++' failed with exit status 1

FĂĽhren Sie danach sip-Wheel mit der neuen Befehlszeilenoption aus:

sip-wheel --foo-include-dir ".../foo" --foo-library-dir ".../foo/bin"

Anstelle der Auslassungspunkte mĂĽssen Sie den Pfad zu dem Ordner angeben, in den Sie den foo- Ordner mit der zusammengestellten Bibliothek verschoben haben . Infolgedessen sollte es der Assembly gelingen, die .whl-Datei zu erstellen. Das erstellte Modul kann auf die gleiche Weise wie in den vorherigen Abschnitten installiert und getestet werden.

ĂśberprĂĽfen Sie die Reihenfolge der aufrufenden Methoden aus project.py


Das nächste Beispiel, das wir betrachten werden, ist sehr einfach und zeigt die Reihenfolge des Aufrufs der Methoden der Project- Klasse , die wir in den vorherigen Abschnitten überladen haben. Dies kann hilfreich sein, um zu verstehen, wann Variablen initialisiert werden können. Dieses Beispiel befindet sich im Ordner pyfoo_c_06 im Quellrepository .

In diesem Beispiel werden im Wesentlichen alle Methoden ĂĽberladen, die wir zuvor in der FooProject- Klasse verwendet haben , die sich in der Datei project.py befindet , und der Druckfunktion Aufrufe hinzugefĂĽgt , die den Namen der Methode anzeigen, in der sie sich befindet:

from sipbuild import Project

class FooProject(Project):
    def get_options(self):
        print('get_options()')
        options = super().get_options()
        return options

    def apply_user_defaults(self, tool):
        print('apply_user_defaults()')
        super().apply_user_defaults(tool)

    def apply_nonuser_defaults(self, tool):
        print('apply_nonuser_defaults()')
        super().apply_nonuser_defaults(tool)

    def update(self, tool):
        print('update()')
        super().update(tool)

    def build(self):
        print('build()')
        super().build()

    def build_sdist(self, sdist_directory):
        print('build_sdist()')
        return super().build_sdist(sdist_directory)

    def build_wheel(self, wheel_directory):
        print('build_wheel()')
        return super().build_wheel(wheel_directory)

    def install(self):
        print('install()')
        super().install()

Aufmerksame Leser sollten beachten, dass zusätzlich zu den zuvor verwendeten Methoden die Methode apply_nonuser_defaults () , über die wir zuvor noch nicht gesprochen haben , in diesem Beispiel überladen ist . Diese Methode empfiehlt, Standardwerte für alle Variablen festzulegen, die nicht über Befehlszeilenparameter geändert werden können. Geben Sie in der

Datei pyproject.toml den expliziten Pfad zur Bibliothek zurĂĽck:

[build-system]
requires = ["sip >=5, <6"]
build-backend = "sipbuild.api"

[tool.sip.metadata]
name = "pyfoo"
version = "0.4"
license = "MIT"

[tool.sip.bindings.pyfoo]
headers = ["foo.h"]
libraries = ["foo"]
include-dirs = ["foo"]
library-dirs = ["foo/bin"]

Damit das Projekt erfolgreich erstellt werden kann, mĂĽssen Sie in den Ordner foo gehen und dort die Bibliothek mit dem Befehl make erstellen . Kehren Sie danach zum Ordner pyfoo_c_06 zurĂĽck und fĂĽhren Sie beispielsweise den Befehl sip- whe aus . Wenn Sie die Compiler-Warnungen verwerfen, wird der folgende Text angezeigt:

get_options ()
apply_nonuser_defaults ()
get_options ()
get_options ()
apply_user_defaults ()
get_options ()
update ()
Diese Bindungen werden erstellt: pyfoo.
build_wheel ()
Generieren der Pyfoo-Bindungen ...
Kompilieren des 'foo'-Moduls ...
Das Rad wurde gebaut.

Es werden fette Linien angezeigt, die aus unserer Datei project.py ausgegeben werden . Wir sehen also, dass die Methode get_options mehrmals aufgerufen wird . Dies muss berücksichtigt werden, wenn Sie eine Mitgliedsvariable in der Klasse initialisieren möchten , die von Project abgeleitet ist . Die Methode get_options ist hierfür nicht der beste Ort.

Es ist auch nützlich, sich daran zu erinnern, dass die Methode apply_nonuser_defaults vor der Methode apply_user_defaults aufgerufen wird , d. H. In der Methode apply_user_defaults können bereits Variablen verwendet werden, deren Werte in der Methode apply_nonuser_defaults festgelegt sind .

Danach wird die Update- Methode aufgerufenund ganz am Ende die Methode, die direkt fĂĽr die Montage verantwortlich ist, in unserem Fall build_wheel .

Fazit zum ersten Teil


In diesem Artikel haben wir begonnen, das SIP-Tool zu untersuchen, mit dem Python-Bindungen für in C oder C ++ geschriebene Bibliotheken erstellt werden können. In diesem ersten Teil des Artikels haben wir die Grundlagen der Verwendung von SIP anhand des Beispiels zum Erstellen einer Python-Bindung für eine in C geschriebene sehr einfache Bibliothek untersucht.

Wir haben die Dateien ermittelt, die Sie für die Arbeit mit SIP erstellen müssen. Die Datei pyproject.toml enthält Informationen zum Paket (Name, Versionsnummer, Lizenz und Pfade zu Header- und Objektdateien). Mit der Datei project.py können Sie den Erstellungsprozess des Python-Pakets beeinflussen, z. B. mit dem Erstellen der C-Bibliothek beginnen oder den Benutzer den Speicherort der Header- und Objektdateien der Bibliothek angeben lassen.

In der * .sip- Dateibeschreibt die Schnittstelle des Python-Moduls und listet die Funktionen und Klassen auf, die im Modul enthalten sein werden. Anweisungen und Anmerkungen werden verwendet , um die Schnittstelle in der * .sip- Datei zu beschreiben .

Im zweiten Teil des Artikels werden wir eine Bindung ĂĽber eine in C ++ geschriebene objektorientierte Bibliothek erstellen und anhand ihres Beispiels Techniken untersuchen, die bei der Beschreibung der Schnittstelle von C ++ - Klassen hilfreich sind, und gleichzeitig neue Anweisungen und Anmerkungen fĂĽr uns behandeln.

Fortsetzung folgt.

Verweise



All Articles