Parfois, en travaillant sur un projet en Python, on souhaite utiliser une bibliothĂšque qui n'est pas Ă©crite en Python, mais, par exemple, en C ou C ++. Les raisons peuvent ĂȘtre diffĂ©rentes. PremiĂšrement, Python est un langage merveilleux, mais dans certaines situations, il n'est pas assez rapide. Et si vous voyez que les performances sont limitĂ©es par les fonctionnalitĂ©s du langage Python, alors il est logique d'Ă©crire une partie du programme dans un autre langage (dans cet article, nous parlerons de C et C ++), d'organiser cette partie du programme en tant que bibliothĂšque, de crĂ©er des liaisons Python (liaisons Python) par dessus et utiliser le module ainsi obtenu comme une bibliothĂšque Python normale. DeuxiĂšmement, une situation se produit souvent lorsque vous savez qu'il existe une bibliothĂšque qui rĂ©sout le problĂšme requis, mais, malheureusement, cette bibliothĂšque n'est pas Ă©crite en Python, mais dans le mĂȘme C ou C ++.Dans ce cas, nous pouvons Ă©galement crĂ©er une liaison Python sur la bibliothĂšque et l'utiliser sans penser au fait que la bibliothĂšque n'a pas Ă©tĂ© Ă©crite Ă l'origine en Python.Il existe diffĂ©rents outils pour crĂ©er des liaisons Python, allant de celles de niveau infĂ©rieur comme l' API Python / C Ă celles de niveau supĂ©rieur comme SWIG et SIP .Je n'avais pas pour objectif de comparer diffĂ©rentes façons de crĂ©er des liaisons Python, mais je voudrais parler des bases de l'utilisation d'un seul outil, Ă savoir SIP . Initialement, SIP a Ă©tĂ© conçu pour crĂ©er une liaison autour de la bibliothĂšque Qt - PyQt , et est Ă©galement utilisĂ© pour dĂ©velopper d'autres grandes bibliothĂšques Python, par exemple, wxPython .Dans cet article, gcc sera utilisĂ© comme compilateur pour C, et g ++ sera utilisĂ© comme compilateur C ++. Tous les exemples ont Ă©tĂ© testĂ©s sous Arch Linux et Python 3.8. Afin de ne pas compliquer les exemples, le sujet de la compilation pour diffĂ©rents systĂšmes d'exploitation et l'utilisation de diffĂ©rents compilateurs (par exemple, Visual Studio) n'est pas inclus dans la portĂ©e de cet article.Vous pouvez tĂ©lĂ©charger tous les exemples de cet article depuis le rĂ©fĂ©rentiel sur github .Le rĂ©fĂ©rentiel avec les sources SIP est situĂ© sur https://www.riverbankcomputing.com/hg/sip/ . Mercurial est utilisĂ© comme systĂšme de contrĂŽle de version pour SIP.Faire une liaison sur une bibliothĂšque en C
Ăcrire une bibliothĂšque en C
Cet exemple se trouve dans le dossier pyfoo_c_01 dans la source, mais dans cet article, nous supposerons que nous faisons tout à partir de zéro.Commençons par un exemple simple. Tout d'abord, nous allons créer une bibliothÚque C simple, que nous exécuterons ensuite à partir d'un script Python. Que notre bibliothÚque soit la seule fonctionint foo(char*);
qui prendra une chaĂźne et retournera sa longueur multipliĂ©e par 2. Lefichier d'en-tĂȘte foo.h peut ressembler, par exemple, Ă ceci:#ifndef FOO_LIB
#define FOO_LIB
int foo(char* str);
#endif
Et le fichier avec l'implémentation de foo.cpp :#include <string.h>
#include "foo.h"
int foo(char* str) {
return strlen(str) * 2;
}
Pour tester la fonctionnalité de la bibliothÚque, nous allons écrire un simple programme main.c :#include <stdio.h>
#include "foo.h"
int main(int argc, char* argv[]) {
char* str = "0123456789";
printf("%d\n", foo(str));
}
Pour plus de précision, créez un 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)/*
Laissez toutes les sources de la bibliothĂšque foo se trouver dans un sous-dossier de foo dans le dossier source:foo_c_01 /
âââ foo
âââ foo.c
âââ foo.h
âââ main.c
âââ Makefile
Nous allons dans le dossier foo et compilons les sources en utilisant la commandemake
Pendant la compilation, le texte sera affiché.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
Le résultat de la compilation sera placé dans le dossier bin à l'intérieur du dossier foo :foo_c_01 /
âââ foo
âââ bin
â âââ foo.o
â âââ libfoo.a
â âââ principal
â âââ main.o
âââ foo.c
âââ foo.h
âââ main.c
âââ Makefile
Nous avons compilé une bibliothÚque de liens statiques et un programme qui l'utilise sous le nom main . AprÚs la compilation, vous pouvez vérifier que le programme principal démarre.Faisons une liaison Python sur la bibliothÚque foo.Bases SIP
Vous devez d'abord installer SIP. Cela se fait de maniĂšre standard, comme avec toutes les autres bibliothĂšques utilisant pip:pip install --user sip
Bien sĂ»r, si vous travaillez dans un environnement virtuel, le paramĂštre --user, indiquant que la bibliothĂšque SIP doit ĂȘtre installĂ©e dans le dossier utilisateur et non globalement dans le systĂšme, ne doit pas ĂȘtre spĂ©cifiĂ©.Que devons-nous faire pour que la bibliothĂšque foo puisse ĂȘtre appelĂ©e Ă partir du code Python? Au minimum, vous devez crĂ©er deux fichiers: l'un au format TOML et nommez-le pyproject.toml , et le second est un fichier avec l'extension .sip. Traitons chacun d'eux sĂ©quentiellement.Nous devons nous mettre d'accord sur la structure de la source. A l' intĂ©rieur du pyfoo_c dossier est le foo dossier , qui contient la source pour la bibliothĂšque. AprĂšs la compilation , le dossier bin est créé dans le dossier foo, qui contiendra tous les fichiers compilĂ©s. Plus tard, nous ajouterons la possibilitĂ© pour l'utilisateur de spĂ©cifier les chemins d'accĂšs aux fichiers d'en-tĂȘte et d'objet de la bibliothĂšque via la ligne de commande.Les fichiers requis pour SIP seront situĂ©s dans le mĂȘme dossier que le dossier foo .pyproject.toml
Le fichier pyproject.toml n'est pas une invention des dĂ©veloppeurs SIP, mais le format de description de projet Python dĂ©crit dans PEP 517 «Un format indĂ©pendant du systĂšme de construction pour les arborescences sources» et dans PEP 518 «SpĂ©cification des exigences minimales du systĂšme de construction pour les projets Python» . Il s'agit d'un fichier TOML , qui peut ĂȘtre considĂ©rĂ© comme une version plus avancĂ©e du format ini, dans lequel les paramĂštres sont stockĂ©s sous la forme de "clĂ© = valeur", et les paramĂštres peuvent ĂȘtre localisĂ©s non seulement dans des sections comme [foo], appelĂ©es tables en termes TOML, mais et dans les sous-sections du formulaire [foo.bar.spam]. Les paramĂštres peuvent contenir non seulement des chaĂźnes, mais Ă©galement des listes, des nombres et des valeurs boolĂ©ennes.Ce fichier est censĂ© dĂ©crire tout ce qui est nĂ©cessaire pour construire un package Python, et pas nĂ©cessairement en utilisant SIP. Cependant, comme nous le verrons un peu plus tard, dans certains cas, ce fichier ne sera pas suffisant, et en plus il faudra crĂ©er un petit script Python. Mais parlons de tout dans l'ordre.Une description complĂšte de tous les paramĂštres possibles du fichier pyproject.toml liĂ©s Ă SIP se trouve sur la page de documentation SIP .Pour notre exemple, crĂ©ez le fichier pyproject.toml au mĂȘme niveau que le dossier foo :foo_c_01 /
âââ foo
â âââ bin
â â âââ foo.o
â â âââ libfoo.a
â â âââ principal
â â âââ main.o
â âââ foo.c
â âââ foo.h
â âââ main.c
â âââ Makefile
âââ pyproject.toml
Le contenu de pyproject.toml sera le suivant:[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"]
La section [build-system] («table» en termes TOML) est standard et est dĂ©crite dans PEP 518 . Il contient deux paramĂštres:D'autres paramĂštres sont dĂ©crits dans les sections [tool.sip. *] .La section [tool.sip.metadata] contient des informations gĂ©nĂ©rales sur le paquet: le nom du paquet Ă construire (notre paquet s'appellera pyfoo , mais ne confondez pas ce nom avec le nom du module, que nous importerons plus tard dans Python), le numĂ©ro de version du paquet (dans notre cas numĂ©ro de version «0.1») et licence (par exemple, « MIT »).Le plus important du point de vue de l'assemblage est dĂ©crit dans les [tool.sip.bindings. pyfoo ].Notez le nom du package dans l'en-tĂȘte de section. Nous avons ajoutĂ© deux paramĂštres Ă cette section:- headers - une liste des fichiers d'en-tĂȘte nĂ©cessaires pour utiliser la bibliothĂšque foo.
- bibliothÚques - une liste de fichiers objets compilés pour la liaison statique.
- include-dirs est le chemin oĂč chercher des fichiers d' en- tĂȘte supplĂ©mentaires en plus de ceux qui sont attachĂ©s au compilateur C. Dans ce cas, oĂč chercher le foo.h fichier .
- bibliothĂšque-dirs est le chemin oĂč rechercher des fichiers objets supplĂ©mentaires en plus de ceux qui sont attachĂ©s au compilateur C. Dans ce cas, il s'agit du dossier dans lequel le fichier de bibliothĂšque compilĂ© foo est créé .
Nous avons donc créé le premier fichier nécessaire pour SIP. Nous passons maintenant à la création du prochain fichier qui décrira le contenu du futur module Python.pyfoo.sip
CrĂ©ez le fichier pyfoo.sip dans le mĂȘme dossier que le fichier pyproject.toml :foo_c_01 /
âââ foo
â âââ bin
â â âââ foo.o
â â âââ libfoo.a
â â âââ principal
â â âââ main.o
â âââ foo.c
â âââ foo.h
â âââ main.c
â âââ Makefile
âââ pyfoo.sip
âââ pyproject.toml
Un fichier avec l'extension .sip dĂ©crit l'interface de la bibliothĂšque source, qui sera convertie en module en Python. Ce fichier a son propre format, que nous allons maintenant considĂ©rer, et ressemble au fichier d'en-tĂȘte C / C ++ avec un balisage supplĂ©mentaire, ce qui devrait aider SIP Ă crĂ©er un module Python.Dans notre exemple, ce fichier doit s'appeler pyfoo.sip , car auparavant, dans le fichier pyproject.toml, nous avons créé le [tool.sip.bindings. pyfoo]. Dans le cas gĂ©nĂ©ral, il peut y avoir plusieurs partitions de ce type et, par consĂ©quent, il doit y avoir plusieurs fichiers * .sip. Mais si nous avons plusieurs fichiers SIP, alors c'est un cas particulier du point de vue de SIP, et nous ne le considĂ©rerons pas dans cet article. Veuillez noter que dans le cas gĂ©nĂ©ral, le nom du fichier .sip (et, par consĂ©quent, le nom de la section) peut ne pas coĂŻncider avec le nom du package spĂ©cifiĂ© dans le paramĂštre name de la section [tool.sip.metadata] .ConsidĂ©rez le fichier pyfoo.sip de notre exemple:%Module(name=foo, language="C")
int foo(char*);
Les lignes qui commencent par le caractĂšre "%" sont appelĂ©es directives. Ils devraient indiquer Ă SIP comment assembler et styliser correctement le module Python. Une liste complĂšte des directives est dĂ©crite sur cette page de documentation . Certaines directives ont des paramĂštres supplĂ©mentaires. Les paramĂštres peuvent ne pas ĂȘtre requis.Dans cet exemple, nous utilisons deux directives; nous dĂ©couvrirons d'autres directives dans les exemples suivants.Le fichier pyfoo.sip commence par la directive % Module (name = foo, language = âCâ) . Veuillez noter que nous avons spĂ©cifiĂ© la valeur du premier paramĂštre ( nom ) sans guillemets, et la valeur du deuxiĂšme paramĂštre ( langue) avec des guillemets, comme des chaĂźnes en C / C ++. Il s'agit d'une exigence de cette directive, comme dĂ©crit dans la documentation de la directive % Module .Dans la directive % Module , seul le paramĂštre name est requis , ce qui dĂ©finit le nom du module Python Ă partir duquel nous importerons la fonction de bibliothĂšque. Dans ce cas, le module s'appelle foo , il contiendra la fonction foo , donc aprĂšs assemblage et installation nous l'importerons en utilisant le code:from foo import foo
Nous pourrions rendre ce module imbriqué dans un autre module en remplaçant cette ligne, par exemple, par ceci:%Module(name=foo.bar, language="C")
...
Ensuite, importez la fonction foo devrait ĂȘtre comme suit:from foo.bar import foo
Le paramĂštre de langue de la directive % Module indique la langue dans laquelle la bibliothĂšque source est Ă©crite. La valeur de ce paramĂštre peut ĂȘtre «C» ou «C ++». Si ce paramĂštre n'est pas spĂ©cifiĂ©, SIP supposera que la bibliothĂšque est Ă©crite en C ++.Regardez maintenant la derniĂšre ligne du fichier pyfoo.sip :int foo(char*);
Il s'agit d'une description de l'interface de la fonction de la bibliothĂšque que nous voulons mettre dans le module Python. Sur la base de cette dĂ©claration, sip crĂ©era une fonction Python. Je pense que tout devrait ĂȘtre clair ici.Nous collectons et vĂ©rifions
Maintenant, tout est prĂȘt pour construire un paquet Python avec une liaison pour une bibliothĂšque C. Tout d'abord, vous devez construire la bibliothĂšque elle-mĂȘme. AccĂ©dez au dossier pyfoo_c_01 / foo / et dĂ©marrez la gĂ©nĂ©ration Ă l'aide de la commande 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
Si tout s'est bien passĂ©, le dossier bin sera créé Ă l'intĂ©rieur du dossier foo , dans lequel, entre autres fichiers, il y aura une bibliothĂšque libfoo.a compilĂ©e . Permettez-moi de vous rappeler qu'ici, afin de ne pas ĂȘtre distrait du sujet principal, nous ne parlons que de construire sous Linux en utilisant gcc. Retournez dans le dossier pyfoo_c_01 . Il est maintenant temps de dĂ©couvrir les Ă©quipes SIP. AprĂšs l'installation de SIP, les commandes de ligne de commande suivantes seront disponibles ( page de documentation ):- sip-build . CrĂ©e un fichier objet d'extension Python.
- sip-install . Crée un fichier objet d'extension Python et l'installe.
- sip-sdist. .tar.gz, pip.
- sip-wheel. wheel ( .whl).
- sip-module. , , SIP. , , . , standalone project, , , , .
- sip-distinfo. .dist-info, wheel.
Ces commandes doivent ĂȘtre exĂ©cutĂ©es Ă partir du dossier oĂč se trouve le fichier pyproject.toml .Pour commencer, pour mieux comprendre le fonctionnement de SIP, exĂ©cutez la commande sip-build , avec l'option --verbose pour une sortie plus dĂ©taillĂ©e sur la console, et voyez ce qui se passe pendant le processus de construction.$ sip-build --verboseCes liaisons seront construites: pyfoo.GĂ©nĂ©ration des liaisons pyfoo ...Compilation du module 'foo' ...construction de l'extension 'foo'crĂ©ation de buildcrĂ©ation de build / temp.linux-x86_64-3.8gcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -march = x86-64 -mtune = gĂ©nĂ©rique -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.osipfoocmodule.c: Dans la fonctionfunc_foo : sipfoocmodule .c: 29: 22: avertissement: dĂ©claration de fonction implicite «foo» [-Wimplicit-function-declaration]29 | sipRes = foo (a0);| ^ ~~gcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -march = x86-64 -mtune = gĂ©nĂ©rique -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.ogcc -pthread -Wno -used-result -Wsign -comparer -DNDEBUG -g -fwrapv -O3 -Mur -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -fno-sĂ©mantique-interposition -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -march = x86-64 -mtune = gĂ©nĂ©rique -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.ogcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -march = x86-64 -mtune = gĂ©nĂ©rique -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.ogcc -pthread -Wno-non-result-result -Wsign -comparer -DNDEBUG -g -fwrapv -O3 -Mur -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -fno-sĂ©mantique-interposition -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -march = x86-64 -mtune = gĂ©nĂ©rique -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.ogcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -march = x86-64 -mtune = gĂ©nĂ©rique -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.ogcc -pthread -Wno-non utilisĂ©-rĂ©sultat -Wsign -comparer -DNDEBUG -g -fwrapv -O3 -Mur -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -fno-sĂ©mantique-interposition -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -march = x86-64 -mtune = gĂ©nĂ©rique -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.ogcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -march = x86-64 -mtune = gĂ©nĂ©rique -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.ogcc -pthread -Wno-non-result-result -Wsign -comparer -DNDEBUG -g -fwrapv -O3 -Mur -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -fno-sĂ©mantique-interposition -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c descriptors.c -o build / temp.linux-x86_64-3.8 / descriptors.ogcc -pthread -Wno-non-result-result -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generic -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -march = x86-64 -mtune = gĂ©nĂ©rique -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-non utilisĂ©-result -Wsign -comparer -DNDEBUG -g -fwrapv -O3 -Mur -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -fno-sĂ©mantique-interposition -march = x86-64 -mtune = gĂ©nĂ©rique -O3 -pipe -fno-plt -march = x86-64 -mtune = gĂ©nĂ©rique -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.osiplib.c: Dans la fonction "slot_richcompare":siplib.c: 9536: 16: avertissement: "st" peut ĂȘtre utilisĂ© sans initialisation dans cette fonction [-Wmaybe-uninitialized]9536 | slot = findSlotInClass (ctd, st);| ^ ~~~~~~~~~~~~~~~~~~~~~~~~siplib.c: 10671: 19: remarque: "st" a Ă©tĂ© dĂ©clarĂ© ici10671 | sipPySlotType st;| ^ ~siplib.c: Dans la fonction "parsePass2":siplib.c: 5625: 32: avertissement: "propriĂ©taire" peut ĂȘtre utilisĂ© sans initialisation dans cette fonction [-Wmaybe-uninitialized]5625 | * propriĂ©taire = arg;| ~~~~~~~ ^ ~g ++ -pthread -shared -Wl, -O1, - sort-common, - as-needed, -z, relro, -z, now -fno-semantic-interposition -Wl, -O1, - sort-common, --as-needed, -z, relro, -z, now 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 / descriptors.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.soLe projet a Ă©tĂ© construit.Nous n'entrerons pas en profondeur dans le travail de SIP, mais il ressort de la sortie que certaines sources sont en train de compiler. Ces sources peuvent ĂȘtre vues dans le dossier build / foo / créé par cette commande :pyfoo_c_01
âââ construire
â âââ foo
â âââ apiversions.c
â âââ array.c
â âââ array.h
â âââ bool.cpp
â âââ construire
â â âââ temp.linux-x86_64-3.8
â â âââ apiversions.o
â â âââ array.o
â â âââ bool.o
â â âââ descriptors.o
â â âââ int_convertors.o
â â âââ objmap.o
â â âââ qtlib.o
â â âââ sipfoocmodule.o
â â âââ siplib.o
â â âââ threads.o
â â âââ voidptr.o
â âââ descriptors.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
â â âââ principal
â â âââ main.o
â âââ foo.c
â âââ foo.h
â âââ main.c
â âââ Makefile
âââ pyfoo.sip
âââ pyproject.toml
Des sources auxiliaires sont apparues dans le dossier build / foo . Par curiosité, regardons le fichier sipfoocmodule.c , car il concerne directement le module foo qui sera créé:/*
* 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;
}
Si vous avez travaillĂ© avec l'API Python / C, vous verrez des fonctions familiĂšres. Portez une attention particuliĂšre Ă la fonction func_foo Ă partir de la ligne 18.Ă la suite de la compilation de ces sources, le fichier build / foo / foo.cpython-38-x86_64-linux-gnu.so sera créé et contient l'extension Python, qui doit encore ĂȘtre installĂ©e correctement.Afin de compiler l'extension et de l'installer immĂ©diatement, vous pouvez utiliser la commande sip-install , mais nous ne l'utilisons pas, car par dĂ©faut, il essaie d'installer l'extension Python créée globalement dans le systĂšme. Cette commande a un paramĂštre --target-dir, avec lequel vous pouvez spĂ©cifier le chemin oĂč vous souhaitez installer l'extension, mais nous ferons mieux d'utiliser d'autres outils qui crĂ©ent des packages, qui peuvent ensuite ĂȘtre installĂ©s Ă l'aide de pip.Tout d'abord, utilisez la commande sip-sdist . Son utilisation est trĂšs simple:$ sip-sdist
The sdist has been built.
AprĂšs cela, le fichier pyfoo-0.1.tar.gz sera créé , qui peut ĂȘtre installĂ© Ă l'aide de la commande:pip install --user pyfoo-0.1.tar.gz
En conséquence, les informations suivantes seront affichées et le package sera installé: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
Assurons-nous que nous avons réussi à créer une liaison Python. Nous démarrons Python et essayons d'appeler la fonction. Permettez-moi de vous rappeler que selon nos paramÚtres, le paquet pyfoo contient le module foo , qui a la fonction foo .>>> from foo import foo
>>> foo(b'123456')
12
Veuillez noter qu'en tant que paramÚtre de la fonction, nous transmettons non seulement une chaßne, mais une chaßne d'octets b'123456 '- un analogue direct de char * en C. Un peu plus tard, nous ajouterons la conversion de char * en str et vice versa. Le résultat était attendu. Permettez-moi de vous rappeler que la fonction foo renvoie la double taille d'un tableau de type char * , qui lui est passée en paramÚtre.Essayons de passer une chaßne Python réguliÚre à la fonction foo au lieu d'une liste d'octets.>>> 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'
La liaison créée n'a pas pu convertir la chaßne en char * ; nous verrons comment lui apprendre comment procéder dans la section suivante.Félicitations, nous avons créé la premiÚre liaison sur une bibliothÚque écrite en C.Quittez l'interpréteur Python et assemblez l'assemblage au format roue. Comme vous le savez sans doute, wheel est un format de package relativement nouveau qui a été utilisé universellement récemment. Le format est décrit dans PEP 427, «The Wheel Binary Package Format 1.0», mais une description des caractéristiques du format de roue est un sujet digne d'un grand article séparé. Il est important pour nous que l'utilisateur puisse facilement installer le package au format roue à l'aide de pip.Un package au format roue n'est pas plus compliqué qu'un package au format sdist. Pour ce faire, dans le dossier contenant le fichierpyproject.toml doit exécuter la commandesip-wheel
AprĂšs avoir exĂ©cutĂ© cette commande, le processus de gĂ©nĂ©ration sera affichĂ© et il peut y avoir des avertissements du compilateur:$ sip-wheelCes liaisons seront construites: pyfoo.GĂ©nĂ©ration des liaisons pyfoo ...Compilation du module 'foo' ...sipfoocmodule.c: Dans la fonctionfunc_foo: sipfoocmodule.c: 29:22: avertissement: dĂ©claration de fonction implicite de la fonction foo [-Wimplicit-function-declaration]29 | sipRes = foo (a0);| ^ ~~siplib.c: Dans la fonction "slot_richcompare":siplib.c: 9536: 16: avertissement: "st" peut ĂȘtre utilisĂ© sans initialisation dans cette fonction [-Wmaybe-uninitialized]9536 | slot = findSlotInClass (ctd, st);| ^ ~~~~~~~~~~~~~~~~~~~~~~~~siplib.c: 10671: 19: remarque: "st" a Ă©tĂ© dĂ©clarĂ© ici10671 | sipPySlotType st;| ^ ~siplib.c: Dans la fonction "parsePass2":siplib.c: 5625: 32: avertissement: "propriĂ©taire" peut ĂȘtre utilisĂ© sans initialisation dans cette fonction [-Wmaybe-uninitialized]5625 | * propriĂ©taire = arg;| ~~~~~~~ ^ ~ ~La roue a Ă©tĂ© construite.Lorsque l'assemblage est terminĂ© (notre petit projet se compile rapidement), un fichier avec le nom pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl ou similaire apparaĂźtra dans le dossier du projet . Le nom du fichier gĂ©nĂ©rĂ© peut diffĂ©rer selon votre systĂšme d'exploitation et la version de Python.Maintenant, nous pouvons installer ce package en utilisant pip:pip install --user --upgrade pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl
L'option --upgrade est utilisĂ©e ici afin que pip remplace le module pyfoo installĂ© prĂ©cĂ©demment.De plus, le module foo et le paquet pyfoo peuvent ĂȘtre utilisĂ©s, comme indiquĂ© ci-dessus.Ajouter des rĂšgles de conversion Ă char *
Dans la section prĂ©cĂ©dente, nous avons rencontrĂ© un problĂšme selon lequel la fonction foo ne peut accepter qu'un ensemble d'octets, mais pas de chaĂźnes. Nous allons maintenant corriger cette lacune. Pour ce faire, nous utiliserons un autre outil SIP - les annotations . Les annotations sont utilisĂ©es dans les fichiers .sip et sont appliquĂ©es Ă certains Ă©lĂ©ments de code: fonctions, classes, arguments de fonction, exceptions, variables, etc. Les annotations sont Ă©crites entre des barres obliques: / annotation / .Une annotation peut fonctionner comme un indicateur, qui peut ĂȘtre dans l'Ă©tat dĂ©fini ou non dĂ©fini, par exemple: / ReleaseGIL / , ou certaines annotations doivent ĂȘtre affectĂ©es Ă certaines valeurs, par exemple: / Encoding = "UTF-8" /. Si plusieurs annotations doivent ĂȘtre appliquĂ©es Ă un objet, elles sont sĂ©parĂ©es par des virgules dans des barres obliques: / annotation_1, annotation_2 /.Dans l'exemple suivant, qui se trouve dans le dossier pyfoo_c_02 , ajoutez une fonction de paramĂštre d'annotation de fichier pyfoo.sip foo :%Module(name=foo, language="C")
int foo(char* /Encoding="UTF-8"/);
L'annotation Encoding indique dans quel encodage la chaĂźne Ă passer Ă la fonction doit ĂȘtre encodĂ©e. Les valeurs de cette annotation peuvent ĂȘtre: ASCII, Latin-1, UTF-8 ou None. Si l'annotation d' encodage n'est pas spĂ©cifiĂ©e ou Ă©gale Ă None , le paramĂštre d'une telle fonction n'est soumis Ă aucun encodage et est transmis Ă la fonction telle quelle, mais dans ce cas, le paramĂštre en code Python doit ĂȘtre de type octets , c'est-Ă -dire un tableau d'octets, comme nous l'avons vu dans l'exemple prĂ©cĂ©dent. Si le codage est spĂ©cifiĂ©, ce paramĂštre peut ĂȘtre une chaĂźne (tapez str en Python). L' annotation d' encodage ne peut ĂȘtre appliquĂ©e qu'aux paramĂštres de type char , const char ,char * ou const char * .VĂ©rifions comment la foo fonction du foo le module fonctionne maintenant . Pour ce faire, comme prĂ©cĂ©demment, vous devez d'abord compiler la bibliothĂšque foo en appelant la commande make dans le dossier foo , puis appeler la commande, par exemple, sip-wheel, Ă partir du dossier d'exemple pyfoo_c_02 . Le fichier pyfoo-0.2-cp38-cp38-manylinux1_x86_64.whl ou avec un nom similaire sera créé , qui peut ĂȘtre dĂ©fini Ă l'aide de la commande:pip install --user --upgrade pyfoo-0.2-cp38-cp38-manylinux1_x86_64.whl
Si tout s'est bien passé, démarrez l'interpréteur Python et essayez d'appeler la fonction foo avec un argument de chaßne:>>> from foo import foo
>>> foo(b'qwerty')
12
>>> foo('qwerty')
12
>>> foo('')
24
Tout d'abord, nous nous assurons que l'utilisation d' octets est toujours possible. AprĂšs cela, nous nous assurons que maintenant nous pouvons Ă©galement passer des arguments de chaĂźne Ă la fonction foo . Notez que la fonction foo pour un argument de chaĂźne avec des lettres russes a renvoyĂ© une valeur deux fois plus grande que pour une chaĂźne contenant uniquement des lettres latines. Cela est arrivĂ© parce que la fonction foo ne compte pas la longueur de la chaĂźne en caractĂšres (et la double), mais la longueur du tableau char * , et comme en UTF-8, le codage des lettres russes occupe 2 octets, puis la taille du tableau char * aprĂšs la conversion de Les chaĂźnes Python se sont avĂ©rĂ©es ĂȘtre deux fois plus longues.Bien! Nous avons rĂ©solu le problĂšme avec l'argument de fonctionfoo , mais que se passe-t-il si nous avons des dizaines ou des centaines de ces fonctions dans notre bibliothĂšque, devrez-vous spĂ©cifier l'encodage des paramĂštres pour chacune d'entre elles? Souvent, l'encodage utilisĂ© dans un programme est le mĂȘme, et il n'y a aucune raison pour que diffĂ©rentes fonctions indiquent des encodages diffĂ©rents. Dans ce cas, il est possible de spĂ©cifier un encodage par dĂ©faut dans SIP, et si pour une fonction un encodage est nĂ©cessaire pour un autre, alors il peut ĂȘtre redĂ©fini en utilisant l'annotation Encoding .Pour dĂ©finir l'encodage des paramĂštres de fonction par dĂ©faut, la directive % DefaultEncoding est utilisĂ©e . Son utilisation est illustrĂ©e dans l'exemple situĂ© dans le dossier pyfoo_c_03 .Afin de profiter de la directive % DefaultEncoding , changez le fichier pyfoo.sip, son contenu est maintenant le suivant:%Module(name=foo, language="C")
%DefaultEncoding "UTF-8"
int foo(char*);
Maintenant, si l'argument est une fonction de type char , char * , etc. S'il n'y a pas d'annotation Encoding , alors l'encodage est tirĂ© de la directive % DefaultEncoding , et si ce n'est pas le cas, alors la conversion n'est pas effectuĂ©e, et pour tous les paramĂštres char * , etc. il faut transfĂ©rer non pas des lignes, mais des octets .Un exemple du dossier pyfoo_c_03 est collectĂ© et vĂ©rifiĂ© de la mĂȘme maniĂšre qu'un exemple du dossier pyfoo_c_02 .En bref sur project.py. Automatisez l'assemblage
Jusqu'Ă prĂ©sent, nous avons utilisĂ© deux fichiers utilitaires pour crĂ©er une liaison Python - pyproject.toml et pyfoo.sip . Nous allons maintenant nous familiariser avec un autre de ces fichiers, qui devrait s'appeler project.py . Avec ce script, nous pouvons influencer le processus de construction de notre package. Faisons l'automatisation de la construction. Afin de collecter les exemples pyfoo_c_01 - pyfoo_c_03 des sections prĂ©cĂ©dentes, vous deviez d'abord aller dans le dossier foo / , y compiler Ă l'aide de la commande make , retourner dans le dossier oĂč se trouve le fichier pyproject.toml, puis commencer Ă construire le package Ă l'aide de l'un des commandes sip- * .Maintenant, notre objectif est de nous assurer que lors de l'exĂ©cution des commandes sip-build , sip-sdist et sip-wheel , l'assemblage de la bibliothĂšque C foo est dĂ©marrĂ© en premier , puis la commande elle-mĂȘme est dĂ©jĂ lancĂ©e.Un exemple créé dans cette section se trouve dans le dossier source pyfoo_c_04 .Pour modifier le processus de gĂ©nĂ©ration, nous pouvons dĂ©clarer une classe dans le fichier project.py (le nom de fichier doit ĂȘtre juste celui) qui est dĂ©rivĂ©e de la classe sipbuild.Project . Cette classe possĂšde des mĂ©thodes que nous pouvons remplacer par nous-mĂȘmes. Actuellement, nous nous intĂ©ressons aux mĂ©thodes suivantes:- construire . AppelĂ© lors de l'appel Ă la commande sip-build .
- build_sdist . Appelé lorsque la commande sip-sdist est appelée .
- build_wheel . Appelé lorsque la commande SIP-Wheel est appelée .
- installer . Appelé lorsque la commande sip-install est invoquée .
Autrement dit, nous pouvons redéfinir le comportement de ces commandes. à strictement parler, les méthodes répertoriées sont déclarées dans la classe abstraite sipbuild.AbstractProject , à partir de laquelle la classe dérivée sipbuild.Project est créée .Créez un fichier project.py avec le contenu suivant: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()
Nous avons dĂ©clarĂ© la classe FooProject , dĂ©rivĂ©e de la classe sipbuild.Project, et y avons dĂ©fini les mĂ©thodes build , build_sdist , build_wheel et install . Dans toutes ces mĂ©thodes, nous appelons les mĂ©thodes du mĂȘme nom Ă partir de la classe de base, avant d'appeler la mĂ©thode _build_foo , qui dĂ©marre l'exĂ©cution de la commande make dans le dossier foo .Notez que les mĂ©thodes build_sdist et build_wheel doivent renvoyer le nom du fichier qu'elles ont créé. Ceci n'est pas Ă©crit dans la documentation, mais est indiquĂ© dans les sources SIP.Maintenant, nous n'avons pas besoin d'exĂ©cuter la commande makemanuellement pour construire la bibliothĂšque foo , cela se fera automatiquement.Si vous exĂ©cutez maintenant la commande sip-wheel dans le dossier pyfoo_c_04 , un fichier est créé avec le nom pyfoo-0.4-cp38-cp38-manylinux1_x86_64.whl ou similaire selon votre systĂšme d'exploitation et la version de Python. Ce package peut ĂȘtre installĂ© Ă l'aide de la commande:pip install --user --upgrade pyfoo-0.4-cp38-cp38-manylinux1_x86_64.whl
AprĂšs cela, vous pouvez vous assurer que la foo fonction du foo le module fonctionne toujours.Ajouter des options de ligne de commande Ă construire
L'exemple suivant se trouve dans le dossier pyfoo_c_05 et le package a un numĂ©ro de version de 0,5 (voir les paramĂštres dans le fichier pyproject.toml ). Cet exemple est basĂ© sur un exemple de la documentation avec quelques corrections. Dans cet exemple, nous allons refaire notre fichier project.py et ajouter de nouvelles options de ligne de commande pour la build.Dans nos exemples, nous construisons une bibliothĂšque trĂšs simple foo, et dans les projets rĂ©els, la bibliothĂšque peut ĂȘtre assez volumineuse et il ne sera alors pas judicieux de l'inclure dans le code source du projet de liaison Python. Permettez-moi de vous rappeler que SIP a Ă©tĂ© créé Ă l'origine pour crĂ©er une liaison pour une bibliothĂšque aussi Ă©norme que Qt. Vous pouvez, bien sĂ»r, affirmer que les sous-modules de git peuvent aider Ă organiser la source, mais ce n'est pas le but. Supposons que la bibliothĂšque ne se trouve pas dans le dossier avec la source de liaison. Dans ce cas, la question se pose: oĂč le collecteur SIP doit-il rechercher l'en-tĂȘte de bibliothĂšque et les fichiers objets? Dans ce cas, diffĂ©rents utilisateurs peuvent avoir leurs propres façons de placer la bibliothĂšque.Pour rĂ©soudre ce problĂšme, nous allons ajouter deux nouvelles options de ligne de commande au systĂšme de construction, avec lesquelles vous pouvez spĂ©cifier le chemin d'accĂšs au fichier foo.h (paramĂštre --foo-include-dir) et au fichier objet de la bibliothĂšque (paramĂštre --foo-library-dir ). De plus, nous supposerons que si ces paramĂštres ne sont pas spĂ©cifiĂ©s, la bibliothĂšque foo est toujours localisĂ©e avec les sources de liaison.Nous devons recrĂ©er le fichier project.py et y dĂ©clarer une classe dĂ©rivĂ©e de sipbuild.Project . Examinons d'abord la nouvelle version du fichier project.py , puis voyons comment cela fonctionne.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):
""" . """
foo_bindings = self.bindings['pyfoo']
if self.foo_include_dir is not None:
foo_bindings.include_dirs = [self.foo_include_dir]
if self.foo_library_dir is not None:
foo_bindings.library_dirs = [self.foo_library_dir]
super().update(tool)
Nous avons Ă nouveau créé la classe FooProject , dĂ©rivĂ©e de sipbuild.Project . Dans cet exemple, l'assemblage automatique de la bibliothĂšque foo est dĂ©sactivĂ© , car il est dĂ©sormais supposĂ© qu'il peut se trouver Ă un autre endroit et au moment de la crĂ©ation de la liaison, les fichiers d'en-tĂȘte et d'objet doivent ĂȘtre prĂȘts. Trois mĂ©thodes sont redĂ©finisdans la FooProject classe : get_options , apply_user_defaults, et mise Ă jour . ConsidĂ©rez-les plus attentivement.Commençons par la mĂ©thode get_options . Cette mĂ©thode doit renvoyer une liste d'instances de la classe sipbuild.Option. Chaque Ă©lĂ©ment de liste est une option de ligne de commande. Dans la mĂ©thode remplacĂ©e, nous obtenons la liste des options par dĂ©faut (la variable options ) en appelant la mĂ©thode de classe de base du mĂȘme nom, puis crĂ©ons deux nouvelles options ( --foo_include_dir et --foo_library_dir ) et les ajoutons Ă la liste, puis renvoyons cette liste Ă partir de la fonction.Le constructeur de la classe Option accepte un paramĂštre obligatoire (nom de l'option) et un nombre suffisamment important de paramĂštres facultatifs qui dĂ©crivent le type de valeur pour ce paramĂštre, la valeur par dĂ©faut, la description du paramĂštre et quelques autres. Cet exemple utilise les options suivantes pour le constructeur Option :- help , , sip-wheel -h
- metavar â , , . metavar «DIR», , â .
- default â . , , foo , ( ).
- tools â , . sip-build, sip-install, sip-sdist sip-wheel, tools = ['build', 'install', 'sdist', 'wheel'].
La mĂ©thode apply_user_defaults surchargĂ©e suivante est conçue pour dĂ©finir des valeurs de paramĂštre que l'utilisateur peut transmettre via la ligne de commande. La mĂ©thode apply_user_defaults de la classe de base crĂ©e une variable (membre de classe) pour chaque paramĂštre de ligne de commande créé dans la mĂ©thode get_options , il est donc important d'appeler la mĂ©thode de classe de base du mĂȘme nom avant d'utiliser les variables créées afin que toutes les variables créées Ă l'aide des paramĂštres de ligne de commande soient créées et initialisĂ©es avec des valeurs par dĂ©faut . AprĂšs cela, dans notre exemple, les variables self.foo_include_dir et self.foo_library_dir seront créées. Si l'utilisateur n'a pas spĂ©cifiĂ© les paramĂštres de ligne de commande correspondants, il prendra alors les valeurs par dĂ©faut en fonction des paramĂštres du constructeur de la classe Option (paramĂštre par dĂ©faut ). Si le paramĂštre par dĂ©faut n'est pas dĂ©fini, en fonction du type de la valeur de paramĂštre attendue, il sera initialisĂ© soit Aucun, soit une liste vide, ou 0.Dans la mĂ©thode apply_user_defaults , nous nous assurons que les chemins dans les variables self.foo_include_dir et self.foo_library_dir sont toujours absolus. Cela est nĂ©cessaire pour qu'il ne dĂ©pende pas de ce que sera le dossier de travail au moment du dĂ©marrage de l'assemblage.La derniĂšre mĂ©thode surchargĂ©e de cette classe est update.. Cette mĂ©thode est appelĂ©e lorsqu'il est nĂ©cessaire d'appliquer au projet les modifications apportĂ©es auparavant. Par exemple, modifiez ou ajoutez les paramĂštres spĂ©cifiĂ©s dans le fichier pyproject.toml . Dans les exemples prĂ©cĂ©dents, nous avons dĂ©fini les chemins d'accĂšs aux fichiers d'en-tĂȘte et d'objet Ă l'aide des paramĂštres include-dirs et library-dirs , respectivement, dans la section [tool.sip.bindings.pyfoo] . Maintenant, nous allons dĂ©finir ces paramĂštres Ă partir du script project.py , donc dans le fichier pyproject.toml , nous supprimerons ces paramĂštres:[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"]
Dans la mĂ©thode mise Ă jour nous du dictionnaire self.bindings clavetĂ© pyfoo gat exemple sipbuild.Bindings . Le nom de la clĂ© correspond aux [tool.sip.bindings. pyfoo ] du fichier pyproject.toml , et l'instance de classe ainsi obtenue dĂ©crit les paramĂštres dĂ©crits dans cette section. Ensuite, les membres de cette classe include_dirs et library_dirs (les noms des membres correspondent aux paramĂštres include-dirs et library-dirs avec un trait d'union remplacĂ© par des traits de soulignement) reçoivent des listes contenant les chemins stockĂ©s dans les membres self.foo_include_dir et self.foo_library_dir. Dans cet exemple, pour plus de prĂ©cision, nous vĂ©rifions que les valeurs self.foo_include_dir et self.foo_library_dir ne sont pas Ă©gales Ă None , mais dans cet exemple, cette condition est toujours remplie car les paramĂštres de ligne de commande que nous avons créés ont des valeurs par dĂ©faut.Ainsi, nous avons prĂ©parĂ© les fichiers de configuration afin que lors de l'assemblage, il soit possible d'indiquer les chemins d'accĂšs aux fichiers d'en-tĂȘte et d'objet. VĂ©rifions ce qui s'est passĂ©.Tout d'abord, assurez-vous que les valeurs par dĂ©faut fonctionnent. Pour ce faire, accĂ©dez au dossier pyfoo_c_05 / foo et crĂ©ez la bibliothĂšque Ă l'aide de la commande make , car nous avons dĂ©sactivĂ© la gĂ©nĂ©ration automatique de bibliothĂšque dans cet exemple.AprĂšs cela, allez dans le dossierpyfoo_c_05 et exĂ©cutez la commande sip-wheel . Ă la suite de cette commande, le fichier pyfoo-0.5-cp38-cp38-manylinux1_x86_64.whl ou avec un nom similaire sera créé . DĂ©placezmaintenant le dossier foo quelque part en dehors du dossier pyfoo_c_05 et exĂ©cutez Ă nouveau la commande sip-wheel . Par consĂ©quent, nous obtenons l'erreur attendue informant que nous n'avons pas de fichier de bibliothĂšque d'objets:usr/bin/ld: -lfoo
collect2: : ld 1
sip-wheel: Unable to compile the 'foo' module: command 'g++' failed with exit status 1
AprÚs cela, exécutez sip-wheel en utilisant la nouvelle option de ligne de commande:sip-wheel --foo-include-dir ".../foo" --foo-library-dir ".../foo/bin"
Au lieu des points de suspension, vous devez spĂ©cifier le chemin d'accĂšs au dossier dans lequel vous avez dĂ©placĂ© le dossier foo avec la bibliothĂšque assemblĂ©e. Par consĂ©quent, l'assembly doit rĂ©ussir Ă crĂ©er le fichier .whl. Le module créé peut ĂȘtre installĂ© et testĂ© de la mĂȘme maniĂšre que dans les sections prĂ©cĂ©dentes.VĂ©rifiez l'ordre des mĂ©thodes d'appel Ă partir de project.py
L'exemple suivant, que nous considĂ©rerons, sera trĂšs simple; il montrera l'ordre d'appeler les mĂ©thodes de la classe Project , que nous avons surchargĂ©es dans les sections prĂ©cĂ©dentes. Cela peut ĂȘtre utile pour comprendre quand les variables peuvent ĂȘtre initialisĂ©es. Cet exemple se trouve dans le dossier pyfoo_c_06 du rĂ©fĂ©rentiel source.L'essence de cet exemple est de surcharger toutes les mĂ©thodes que nous avons utilisĂ©es auparavant dans la classe FooProject , qui se trouve dans le fichier project.py , et de leur ajouter des appels Ă la fonction d' impression , qui afficheraient le nom de la mĂ©thode dans laquelle il se trouve: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()
Les lecteurs attentifs doivent noter qu'en plus des mĂ©thodes prĂ©cĂ©demment utilisĂ©es, la mĂ©thode apply_nonuser_defaults () , dont nous n'avons pas parlĂ© auparavant , est surchargĂ©e dans cet exemple . Cette mĂ©thode recommande de dĂ©finir des valeurs par dĂ©faut pour toutes les variables qui ne peuvent pas ĂȘtre modifiĂ©es via les paramĂštres de ligne de commande.Dans le fichier pyproject.toml, renvoyez le chemin explicite vers la bibliothĂšque:[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"]
Pour que le projet rĂ©ussisse, vous devez aller dans le dossier foo et y construire la bibliothĂšque Ă l'aide de la commande make . Ensuite, retournez dans le dossier pyfoo_c_06 et exĂ©cutez, par exemple, la commande sip-wheel . Par consĂ©quent, si vous ignorez les avertissements du compilateur, le texte suivant s'affiche:get_options ()apply_nonuser_defaults ()get_options ()get_options ()apply_user_defaults ()get_options ()update ()Ces liaisons seront créées: pyfoo.build_wheel ()GĂ©nĂ©ration des liaisons pyfoo ...Compilation du module 'foo' ...La roue a Ă©tĂ© construite.Des lignes en gras sont affichĂ©es qui sont sorties de notre fichier project.py . Ainsi, nous voyons que la mĂ©thode get_options est appelĂ©e plusieurs fois, et cela doit ĂȘtre pris en compte si vous allez initialiser une variable membre de la classe dĂ©rivĂ©e de Project . La mĂ©thode get_options n'est pas le meilleur endroit pour cela.Il est Ă©galement utile de se rappeler que la mĂ©thode apply_nonuser_defaults est appelĂ©e avant la mĂ©thode apply_user_defaults , c'est-Ă -dire dans la mĂ©thode apply_user_defaults , il est dĂ©jĂ possible d'utiliser des variables dont les valeurs sont dĂ©finies dans la mĂ©thode apply_nonuser_defaults .AprĂšs cela, la mĂ©thode de mise Ă jour est appelĂ©e, et Ă la toute fin, la mĂ©thode directement responsable de l'assemblage, dans notre cas, build_wheel .Conclusion de la premiĂšre partie
Dans cet article, nous avons commencĂ© Ă Ă©tudier l'outil SIP conçu pour crĂ©er des liaisons Python pour des bibliothĂšques Ă©crites en C ou C ++. Dans cette premiĂšre partie de l'article, nous avons examinĂ© les bases de l'utilisation de SIP en utilisant l'exemple de crĂ©ation d'une liaison Python pour une bibliothĂšque trĂšs simple Ă©crite en C.Nous avons dĂ©terminĂ© les fichiers que vous devez crĂ©er pour travailler avec SIP. Le fichier pyproject.toml contient des informations sur le package (nom, numĂ©ro de version, licence et chemins d'accĂšs aux fichiers d'en-tĂȘte et d'objet). En utilisant le fichier project.py , vous pouvez influencer le processus de construction du package Python, par exemple, commencer Ă construire la bibliothĂšque C ou laisser l'utilisateur spĂ©cifier l'emplacement des fichiers d'en-tĂȘte et d'objet de la bibliothĂšque.Dans le fichier * .sipdĂ©crit l'interface du module Python listant les fonctions et classes qui seront contenues dans le module. Des directives et des annotations sont utilisĂ©es pour dĂ©crire l'interface dans le fichier * .sip .Dans la deuxiĂšme partie de l'article, nous allons crĂ©er une liaison sur une bibliothĂšque orientĂ©e objet Ă©crite en C ++, et sur son exemple, nous Ă©tudierons des techniques qui seront utiles pour dĂ©crire l'interface des classes C ++, et en mĂȘme temps nous traiterons de nouvelles directives et annotations pour nous.Ă suivre.RĂ©fĂ©rences