Membuat binding Python untuk pustaka C / C ++ menggunakan SIP. Bagian 1

Terkadang, saat mengerjakan proyek dengan Python, ada keinginan untuk menggunakan pustaka yang tidak ditulis dalam Python, tetapi, misalnya, dalam C atau C ++. Alasan untuk ini mungkin berbeda: Pertama, Python adalah bahasa yang indah, tetapi dalam beberapa situasi itu tidak cukup cepat. Dan jika Anda melihat bahwa kinerja dibatasi oleh fitur-fitur bahasa Python, maka masuk akal untuk menulis bagian dari program dalam bahasa lain (dalam artikel ini kita akan berbicara tentang C dan C ++), mengatur bagian program ini sebagai perpustakaan, membuat binding Python (binding Python) di atasnya dan gunakan modul sehingga diperoleh sebagai perpustakaan Python normal. Kedua, situasi sering terjadi ketika Anda tahu bahwa ada perpustakaan yang memecahkan masalah yang diperlukan, tetapi, sayangnya, perpustakaan ini tidak ditulis dengan Python, tetapi dalam C atau C ++ yang sama.Dalam hal ini, kita juga dapat membuat Python yang mengikat perpustakaan dan menggunakannya tanpa memikirkan fakta bahwa perpustakaan tersebut pada awalnya tidak ditulis dengan Python.

Ada berbagai alat untuk membuat binding Python, mulai dari yang lebih rendah seperti API Python / C hingga yang lebih tinggi seperti SWIG dan SIP .

Saya tidak memiliki tujuan membandingkan berbagai cara membuat binding Python, tetapi saya ingin berbicara tentang dasar-dasar menggunakan satu alat, yaitu SIP . Awalnya, SIP dirancang untuk membuat pengikatan di sekitar perpustakaan Qt - PyQt , dan juga digunakan untuk mengembangkan pustaka Python besar lainnya, misalnya, wxPython .

Pada artikel ini, gcc akan digunakan sebagai kompiler untuk C, dan g ++ akan digunakan sebagai kompiler C ++. Semua contoh diuji di bawah Arch Linux dan Python 3.8. Agar tidak menyulitkan contoh, topik kompilasi untuk sistem operasi yang berbeda dan menggunakan kompiler yang berbeda (misalnya, Visual Studio) tidak termasuk dalam ruang lingkup artikel ini.

Anda dapat mengunduh semua contoh untuk artikel ini dari repositori di github .
Repositori dengan sumber SIP terletak di https://www.riverbankcomputing.com/hg/sip/ . Mercurial digunakan sebagai sistem kontrol versi untuk SIP.

Membuat penjilidan ke perpustakaan di C


Menulis perpustakaan di C


Contoh ini terletak di folder pyfoo_c_01 di sumber, tetapi dalam artikel ini kita akan menganggap bahwa kita melakukan semuanya dari awal.

Mari kita mulai dengan contoh sederhana. Pertama, kita akan membuat pustaka C sederhana, yang kemudian akan kita jalankan dari skrip Python. Biarkan perpustakaan kami menjadi satu-satunya fungsi

int foo(char*);

yang akan mengambil string dan mengembalikan panjangnya dikalikan dengan 2. File

header foo.h mungkin terlihat, misalnya, seperti ini:

#ifndef FOO_LIB
#define FOO_LIB

int foo(char* str);

#endif

Dan file dengan implementasi foo.cpp :

#include <string.h>

#include "foo.h"

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

Untuk menguji fungsionalitas perpustakaan, kami akan menulis program main.c sederhana :

#include <stdio.h>

#include "foo.h"

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

Untuk akurasi, buat 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)/*

Biarkan semua sumber foo perpustakaan berada dalam subfolder dari foo di folder sumber:

foo_c_01 /
└── foo
    β”œβ”€β”€ foo.c
    β”œβ”€β”€ foo.h
    β”œβ”€β”€ main.c
    └── Makefile


Kami masuk ke folder foo dan kompilasi sumber menggunakan perintah

make

Selama kompilasi, teks akan ditampilkan.

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

Hasil kompilasi akan ditempatkan di folder bin di dalam folder foo :

foo_c_01 /
└── foo
    β”œβ”€β”€ bin
    β”‚ β”œβ”€β”€ foo.o
    β”‚ β”œβ”€β”€ libfoo.a
    β”‚ β”œβ”€β”€ utama
    β”‚ └── main.o
    β”œβ”€β”€ foo.c
    β”œβ”€β”€ foo.h
    β”œβ”€β”€ main.c
    └── Makefile


Kami mengkompilasi pustaka untuk tautan statis dan program yang menggunakannya dengan nama utama . Setelah kompilasi, Anda dapat memverifikasi bahwa program utama dimulai.

Mari kita membuat Python yang mengikat perpustakaan foo.

Dasar-dasar SIP


Pertama, Anda perlu menginstal SIP. Ini dilakukan secara standar, seperti semua perpustakaan lain yang menggunakan pip:

pip install --user sip

Tentu saja, jika Anda bekerja di lingkungan virtual, maka parameter --user, yang menunjukkan bahwa perpustakaan SIP perlu diinstal di folder pengguna, dan tidak secara global dalam sistem, tidak boleh ditentukan.

Apa yang perlu kita lakukan agar foo library dapat dipanggil dari kode Python? Minimal, Anda perlu membuat dua file: salah satunya dalam format TOML dan beri nama pyproject.toml , dan yang kedua adalah file dengan ekstensi .sip. Mari kita hadapi masing-masing secara berurutan.

Kita perlu menyetujui struktur sumbernya. Di dalam folder pyfoo_c adalah folder foo , yang berisi sumber untuk perpustakaan. Setelah dikompilasi , folder bin dibuat di dalam folder foo, yang akan berisi semua file yang dikompilasi. Nanti kita akan menambahkan kemampuan bagi pengguna untuk menentukan jalur ke file header dan objek perpustakaan melalui baris perintah.

File yang diperlukan untuk SIP akan berada di folder yang sama dengan folder foo .

pyproject.toml


File pyproject.toml bukan merupakan penemuan pengembang SIP, tetapi format deskripsi proyek Python dijelaskan dalam PEP 517 β€œFormat independen sistem bangun untuk pohon sumber” dan dalam PEP 518 β€œMenentukan Persyaratan Sistem Bangun Minimum untuk Proyek Python” . Ini adalah file TOML , yang dapat dianggap sebagai versi yang lebih maju dari format ini, di mana parameter disimpan dalam bentuk "key = value", dan parameter dapat ditemukan tidak hanya di bagian seperti [foo], yang disebut tabel dalam istilah TOML, tetapi dan di subbagian formulir [foo.bar.spam]. Parameter dapat berisi tidak hanya string, tetapi juga daftar, angka, dan nilai Boolean.

File ini seharusnya menggambarkan semua yang diperlukan untuk membangun paket Python, dan tidak harus menggunakan SIP. Namun, seperti yang akan kita lihat nanti, dalam beberapa kasus file ini tidak akan cukup, dan di samping itu perlu membuat skrip Python kecil. Tapi mari kita bicara tentang semuanya secara berurutan.

Deskripsi lengkap tentang semua parameter yang mungkin dari file pyproject.toml yang terkait dengan SIP dapat ditemukan di halaman dokumentasi SIP .

Sebagai contoh kita, buat file pyproject.toml pada tingkat yang sama dengan folder foo :

foo_c_01 /
β”œβ”€β”€ foo
β”‚ β”œβ”€β”€ bin
β”‚ β”‚ β”œβ”€β”€ foo.o
β”‚ β”‚ β”œβ”€β”€ libfoo.a
β”‚ β”‚ β”œβ”€β”€ utama
β”‚ β”‚ └── main.o
β”‚ β”œβ”€β”€ foo.c
β”‚ β”œβ”€β”€ foo.h
β”‚ β”œβ”€β”€ main.c
β”‚ └── Makefile
└── pyproject.toml


Isi pyproject.toml adalah sebagai berikut:

[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"]

Bagian [build-system] ("table" dalam istilah TOML) adalah standar dan dijelaskan dalam PEP 518 . Ini berisi dua parameter:


Parameter lain dijelaskan dalam Bagian [tool.sip. *] .

Bagian [tool.sip.metadata] berisi informasi umum tentang paket: nama paket yang akan dirakit (paket kami akan disebut pyfoo , tetapi jangan bingung nama ini dengan nama modul, yang nantinya akan kami impor ke Python), nomor versi paket (dalam kasus kami) nomor versi "0,1") dan lisensi (misalnya, " MIT ").

Yang paling penting dari sudut pandang perakitan dijelaskan dalam [tool.sip.bindings. pyfoo ].

Perhatikan nama paket di header bagian. Kami telah menambahkan dua parameter ke bagian ini:

  • header - daftar file header yang diperlukan untuk menggunakan perpustakaan foo.
  • perpustakaan - daftar file objek yang dikompilasi untuk tautan statis.
  • termasuk-dirs adalah jalan mana untuk mencari file header tambahan selain mereka yang melekat pada C compiler. Dalam hal ini, di mana untuk mencari Foo.h berkas .
  • dir-library adalah path tempat mencari file objek tambahan selain yang dilampirkan ke kompiler C. Dalam hal ini, ini adalah folder di mana file library yang dikompilasi foo dibuat .

Jadi, kami membuat file yang diperlukan pertama untuk SIP. Sekarang kita beralih ke membuat file selanjutnya yang akan menjelaskan isi modul Python masa depan.

pyfoo.sip


Buat file pyfoo.sip di folder yang sama dengan file pyproject.toml :

foo_c_01 /
β”œβ”€β”€ foo
β”‚ β”œβ”€β”€ bin
β”‚ β”‚ β”œβ”€β”€ foo.o
β”‚ β”‚ β”œβ”€β”€ libfoo.a
β”‚ β”‚ β”œβ”€β”€ utama
β”‚ β”‚ └── main.o
β”‚ β”œβ”€β”€ foo.c
β”‚ β”œβ”€β”€ foo.h
β”‚ β”œβ”€β”€ main.c
β”‚ └── Makefile
β”œβ”€β”€ pyfoo.sip
└── pyproject.toml


File dengan ekstensi .sip menggambarkan antarmuka pustaka sumber, yang akan dikonversi menjadi modul dengan Python. File ini memiliki format sendiri, yang sekarang akan kita pertimbangkan, dan menyerupai file header C / C ++ dengan markup tambahan, yang seharusnya membantu SIP membuat modul Python.

Dalam contoh kita, file ini harus disebut pyfoo.sip , karena sebelum itu, dalam file pyproject.toml, kami membuat [tool.sip.bindings. pyfoo] Dalam kasus umum, mungkin ada beberapa partisi seperti itu dan, karenanya, harus ada beberapa file * .sip. Tetapi jika kita memiliki beberapa file sip, maka ini adalah kasus khusus dari sudut pandang SIP, dan kami tidak akan mempertimbangkannya dalam artikel ini. Harap perhatikan bahwa dalam kasus umum, nama file .sip (dan, sesuai dengan itu, nama bagian) mungkin tidak sesuai dengan nama paket yang ditentukan dalam parameter nama di bagian [tool.sip.metadata] .

Pertimbangkan file pyfoo.sip dari contoh kami:

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

int foo(char*);

Baris yang dimulai dengan karakter "%" disebut arahan. Mereka harus memberi tahu SIP cara merakit dan mengatur modul Python dengan benar. Daftar lengkap arahan dijelaskan pada halaman dokumentasi ini . Beberapa arahan memiliki parameter tambahan. Parameter mungkin tidak diperlukan.

Dalam contoh ini, kami menggunakan dua arahan, kami akan mengenal beberapa arahan lain dalam contoh berikut.

File pyfoo.sip dimulai dengan direktif % Module (name = foo, language = "C") . Harap dicatat bahwa kami menentukan nilai parameter pertama ( nama ) tanpa tanda kutip, dan nilai parameter kedua ( bahasa) dengan tanda kutip, seperti string dalam C / C ++. Ini adalah persyaratan dari arahan ini seperti yang dijelaskan dalam dokumentasi untuk % direktif Modul .

Dalam direktif % Module , hanya parameter nama yang diperlukan , yang menetapkan nama modul Python dari mana kita akan mengimpor fungsi pustaka. Dalam hal ini, modul ini disebut foo , itu akan berisi fungsi foo , jadi setelah perakitan dan instalasi kita akan mengimpornya menggunakan kode:

from foo import foo

Kita dapat membuat modul ini bersarang di modul lain dengan mengganti baris ini, misalnya, dengan ini:

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

Kemudian impor fungsi foo harus sebagai berikut:

from foo.bar import foo

Parameter bahasa dari direktif % Module menunjukkan bahasa tempat pustaka sumber ditulis. Nilai parameter ini bisa berupa "C" atau "C ++". Jika parameter ini tidak ditentukan, maka SIP akan menganggap bahwa pustaka ditulis dalam C ++.

Sekarang lihat baris terakhir file pyfoo.sip :

int foo(char*);

Ini adalah deskripsi antarmuka fungsi dari perpustakaan yang ingin kita masukkan ke dalam modul Python. Berdasarkan deklarasi ini, sip akan membuat fungsi Python. Saya pikir semuanya harus jelas di sini.

Kami mengumpulkan dan memeriksa


Sekarang semuanya siap untuk membangun paket Python dengan pengikatan untuk pustaka C. Pertama-tama, Anda perlu membangun pustaka itu sendiri. Pergi ke folder pyfoo_c_01 / foo / dan mulai membangun menggunakan perintah 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

Jika semuanya berjalan dengan baik, maka folder bin akan dibuat di dalam folder foo , di mana, di antara file-file lain, akan ada perpustakaan libfoo.a yang dikompilasi . Biarkan saya mengingatkan Anda bahwa di sini, agar tidak teralihkan dari topik utama, kita hanya berbicara tentang membangun di Linux menggunakan gcc. Kembali ke folder pyfoo_c_01 . Sekarang saatnya mengenal tim SIP. Setelah menginstal SIP, perintah baris perintah berikut akan tersedia ( halaman dokumentasi ):



  • sip-build . Membuat file objek ekstensi Python.
  • sip-instal . Membuat file objek ekstensi Python dan menginstalnya.
  • sip-sdist. .tar.gz, pip.
  • sip-wheel. wheel ( .whl).
  • sip-module. , , SIP. , , . , standalone project, , , , .
  • sip-distinfo. .dist-info, wheel.

Perintah-perintah ini harus dijalankan dari folder di mana file pyproject.toml berada .

Untuk memulai, untuk lebih memahami operasi SIP, jalankan perintah sip-build , dengan opsi --verbose untuk output yang lebih detail ke konsol, dan lihat apa yang terjadi selama proses build.

$ sip-build --verbose

Bindings ini akan dibuat: pyfoo.
Menghasilkan binding pyfoo ...
Mengkompilasi modul 'foo' ... membuat
ekstensi 'foo'
membuat
pembuatan build build / temp.linux-x86_64-3.8
gcc -pthread -Wno-tidak digunakan-hasil -Wsign-bandingkan -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -march = x86-64 -mtune = generik -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: Dalam fungsi
func_foo : sipfoocmodule .c: 29: 22: peringatan: deklarasi fungsi implisit β€œfoo” [-Wimplicit-function-declaration]
29 | sipRes = foo (a0);
| ^ ~~
gcc -pthread -Wno-tidak digunakan-hasil -Wsign-bandingkan -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -march = x86-64 -mtune = generik -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-tidak digunakan-hasil -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = publik -I. -I ../../ foo -I / usr / include / python3.8 -c bool.cpp -o build / temp.linux-x86_64-3.8 / bool.o
gcc -pthread -Wno-tidak digunakan-hasil -Wsign-bandingkan -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -march = x86-64 -mtune = generik -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-tidak digunakan-hasil -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = publik -I. -I ../../ foo -I / usr / include / python3.8 -c qtlib.c -o build / temp.linux-x86_64-3.8 / qtlib.o
gcc -pthread -Wno-tidak digunakan-hasil -Wsign-bandingkan -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -march = x86-64 -mtune = generik -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-tidak digunakan-hasil -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = publik -I. -I ../../ foo -I / usr / include / python3.8 -c voidptr.c -o build / temp.linux-x86_64-3.8 / voidptr.o
gcc -pthread -Wno-tidak digunakan-hasil -Wsign-bandingkan -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -march = x86-64 -mtune = generik -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-tidak digunakan-hasil -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = publik -I. -I ../../ foo -I / usr / include / python3.8 -c descriptors.c -o build / temp.linux-x86_64-3.8 / descriptors.o
gcc -pthread -Wno-tidak digunakan-hasil -Wsign-bandingkan -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -march = x86-64 -mtune = generik -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-tidak digunakan-hasil -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -march = x86-64 -mtune = generik -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = publik -I. -I ../../ foo -I / usr / include / python3.8 -c siplib.c -o build / temp.linux-x86_64-3.8 / siplib.o
siplib.c: Dalam fungsi "slot_richcompare":
siplib.c: 9536: 16: peringatan: "st" dapat digunakan tanpa inisialisasi dalam fungsi ini [-Wmaybe-uninitialized]
9536 | slot = findSlotInClass (ctd, st);
| ^ ~~~~~~~~~~~~~~~~~~~~~~~
siplib.c: 10671: 19: note: "st" dideklarasikan di sini
10671 | sipPySlotType st;
| ^ ~
siplib.c: Dalam fungsi "parsePass2":
siplib.c: 5625: 32: peringatan: "pemilik" dapat digunakan tanpa inisialisasi dalam fungsi ini [-Wmaybe-uninitialized]
5625 | * pemilik = arg;
| ~~~~~~~ ^ ~
g ++ -pthread-shared -Wl, -O1, - sort-common, - sesuai kebutuhan, -z, relro, -z, sekarang -fno-semantic-interposition -Wl, -O1, - sort-common, --sebagai diperlukan, -z, relro, -z, sekarang 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 / proyek / soft / sip-contoh / pyfoo_c_01 / build / foo / foo. cpython-38-x86_64-linux-gnu.so
Proyek ini telah dibangun.

Kami tidak akan masuk jauh ke dalam pekerjaan SIP, tetapi dapat dilihat dari output yang dikompilasi oleh beberapa sumber. Sumber-sumber ini dapat dilihat di folder build / foo / yang dibuat oleh perintah ini :

pyfoo_c_01
β”œβ”€β”€ membangun
β”‚ └── foo
β”‚ β”œβ”€β”€ perpisahan.c
β”‚ β”œβ”€β”€ array.c
β”‚ β”œβ”€β”€ array.h
β”‚ β”œβ”€β”€ bool.cpp
β”‚ β”œβ”€β”€ membangun
β”‚ β”‚ └── temp.linux-x86_64-3.8
β”‚ β”‚ β”œβ”€β”€ perpisahan.o
β”‚ β”‚ β”œβ”€β”€ array.o
β”‚ β”‚ β”œβ”€β”€ bool.o
β”‚ β”‚ β”œβ”€β”€ deskriptor.o
β”‚ β”‚ β”œβ”€β”€ int_convertors.o
β”‚ β”‚ β”œβ”€β”€ objmap.o
β”‚ β”‚ β”œβ”€β”€ qtlib.o
β”‚ β”‚ β”œβ”€β”€ sipfoocmodule.o
β”‚ β”‚ β”œβ”€β”€ siplib.o
β”‚ β”‚ β”œβ”€β”€ threads.o
β”‚ β”‚ └── voidptr.o
β”‚ β”œβ”€β”€ deskriptor.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
β”‚ β”‚ β”œβ”€β”€ utama
β”‚ β”‚ └── main.o
β”‚ β”œβ”€β”€ foo.c
β”‚ β”œβ”€β”€ foo.h
β”‚ β”œβ”€β”€ main.c
β”‚ └── Makefile
β”œβ”€β”€ pyfoo.sip
└── pyproject.toml


Sumber tambahan muncul di folder build / foo . Karena penasaran, mari kita lihat file sipfoocmodule.c , karena langsung berhubungan dengan modul foo yang akan dibuat:

/*
 * 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;
}

Jika Anda bekerja dengan API Python / C, Anda akan melihat fungsi yang sudah dikenal. Perhatikan fungsi func_foo mulai dari baris 18.

Sebagai hasil kompilasi dari sumber-sumber ini, file build / foo / foo.cpython-38-x86_64-linux-gnu.so akan dibuat , dan berisi ekstensi Python, yang masih perlu diinstal dengan benar.

Untuk mengkompilasi ekstensi dan menginstalnya segera, Anda dapat menggunakan perintah sip-install , tetapi kami tidak akan menggunakannya, karena secara default ia mencoba untuk menginstal ekstensi Python yang dibuat secara global ke dalam sistem. Perintah ini memiliki parameter --target-dir, dengan mana Anda dapat menentukan jalur tempat Anda ingin menginstal ekstensi, tetapi kami lebih baik menggunakan alat lain yang membuat paket, yang kemudian dapat diinstal menggunakan pip.

Pertama, gunakan perintah sip-sdist . Menggunakannya sangat sederhana:

$ sip-sdist

The sdist has been built.

Setelah itu, file pyfoo-0.1.tar.gz akan dibuat , yang dapat diinstal menggunakan perintah:

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

Akibatnya, informasi berikut akan ditampilkan dan paket akan diinstal:

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

Mari kita pastikan bahwa kita berhasil membuat pengikatan Python. Kami memulai Python dan mencoba memanggil fungsinya. Biarkan saya mengingatkan Anda bahwa menurut pengaturan kami, paket pyfoo berisi modul foo , yang memiliki fungsi foo .

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

Harap dicatat bahwa sebagai parameter ke fungsi, kami melewati bukan hanya string, tetapi string byte b'123456 '- analog langsung char * ke C. Beberapa saat kemudian, kami akan menambahkan konversi char * ke str dan sebaliknya. Hasilnya diharapkan. Biarkan saya mengingatkan Anda bahwa fungsi foo mengembalikan ukuran ganda dari array tipe char * , diteruskan ke parameter.

Mari kita coba meneruskan string Python biasa ke fungsi foo alih-alih daftar byte.

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

Penjilidan yang dibuat tidak dapat mengubah string menjadi char * ; kita akan membahas bagaimana cara mengajarkannya untuk melakukan ini di bagian selanjutnya.

Selamat, kami membuat pengikatan pertama pada perpustakaan yang ditulis dalam C.

Keluar dari juru bahasa Python dan merakit perakitan dalam format roda. Seperti yang kemungkinan besar Anda ketahui, wheel adalah format paket yang relatif baru yang telah digunakan secara universal baru-baru ini. Formatnya dijelaskan dalam PEP 427, β€œPaket Binary Wheel Format 1.0,” tetapi deskripsi fitur dari format roda adalah topik yang layak untuk artikel besar yang terpisah. Penting bagi kami bahwa pengguna dapat dengan mudah menginstal paket dalam format roda menggunakan pip.

Paket dalam format roda tidak lebih rumit dari paket dalam format sdist. Untuk melakukan ini, dalam folder dengan file tersebutpyproject.toml perlu menjalankan perintah

sip-wheel

Setelah menjalankan perintah ini, proses build akan ditampilkan dan mungkin ada peringatan dari kompiler:

$ sip-wheel

binding ini akan dibangun: pyfoo.
Menghasilkan binding pyfoo ...
Mengkompilasi modul 'foo' ...
sipfoocmodule.c: Dalam fungsi
func_foo: sipfoocmodule.c: 29:22: warning: fungsi implisit deklarasi fungsi foo [-Wimplicit-function-declaration]
29 | sipRes = foo (a0);
| ^ ~~
siplib.c: Dalam fungsi "slot_richcompare":
siplib.c: 9536: 16: peringatan: "st" dapat digunakan tanpa inisialisasi dalam fungsi ini [-Wmaybe-uninitialized]
9536 | slot = findSlotInClass (ctd, st);
| ^ ~~~~~~~~~~~~~~~~~~~~~~~
siplib.c: 10671: 19: komentar: "st" dinyatakan di sini
10671 | sipPySlotType st;
| ^ ~
siplib.c: Dalam fungsi "parsePass2":
siplib.c: 5625: 32: peringatan: "pemilik" mungkin digunakan tanpa inisialisasi dalam fungsi ini [-Wmaybe-uninitialized]
5625 | * pemilik = arg;
| ~~~~~~~ ^ ~ ~
Roda telah dibangun.

Ketika perakitan selesai (proyek kecil kami mengkompilasi dengan cepat), sebuah file dengan nama pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl atau yang serupa akan muncul di folder proyek . Nama file yang dihasilkan mungkin berbeda tergantung pada sistem operasi dan versi Python Anda.

Sekarang kita dapat menginstal paket ini menggunakan pip:

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

Opsi --upgrade digunakan di sini sehingga pip menggantikan modul pyfoo yang diinstal sebelumnya.

Selanjutnya, modul foo dan paket pyfoo dapat digunakan, seperti yang ditunjukkan di atas.

Tambahkan aturan konversi ke char *


Di bagian sebelumnya, kami mengalami masalah bahwa fungsi foo hanya dapat menerima satu set byte, tetapi bukan string. Sekarang kita akan memperbaiki kekurangan ini. Untuk melakukan ini, kita akan menggunakan alat SIP lain - anotasi . Anotasi digunakan di dalam file .sip dan diterapkan ke beberapa elemen kode: fungsi, kelas, argumen fungsi, pengecualian, variabel, dll. Anotasi ditulis di antara garis miring: / annotation / .

Anotasi dapat berfungsi sebagai bendera, yang dapat di set negara atau tidak diatur, misalnya: / ReleaseGIL / , atau beberapa penjelasan perlu diberi beberapa nilai, misalnya: / Encoding = "UTF-8" /. Jika beberapa anotasi perlu diterapkan ke beberapa objek, maka dipisahkan dengan tanda koma di dalam garis miring: / annotation_1, annotation_2 /.

Dalam contoh berikut, yang terletak di folder pyfoo_c_02 , tambahkan file pyfoo.sip fungsi parameter anotasi foo :

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

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

Anotasi Pengkodean menunjukkan di mana pengkodean string yang akan diteruskan ke fungsi harus dikodekan. Nilai untuk anotasi ini mungkin: ASCII, Latin-1, UTF-8, atau Tidak Ada. Jika anotasi Pengkodean tidak ditentukan atau sama dengan Tidak Ada , maka parameter untuk fungsi tersebut tidak mengalami pengkodean apa pun dan diteruskan ke fungsi seperti apa adanya, tetapi dalam hal ini parameter dalam kode Python harus bertipe bytes , yaitu. array byte, seperti yang kita lihat pada contoh sebelumnya. Jika pengkodean ditentukan, maka parameter ini dapat berupa string (ketik str dalam Python). Anotasi pengodean hanya dapat diterapkan ke parameter tipe char , const char ,char * atau const char * .

Mari kita periksa bagaimana fungsi foo dari modul foo sekarang berfungsi . Untuk melakukan ini, seperti sebelumnya, Anda pertama-tama harus mengkompilasi pustaka foo dengan memanggil perintah make di dalam folder foo , dan kemudian memanggil perintah, misalnya, sip-wheel, dari folder contoh pyfoo_c_02 . File pyfoo-0.2-cp38-cp38-manylinux1_x86_64.whl atau dengan nama yang serupa akan dibuat , yang dapat diatur menggunakan perintah:

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

Jika semuanya berjalan dengan baik, jalankan interpreter Python dan coba panggil fungsi foo dengan argumen string:

>>> from foo import foo

>>> foo(b'qwerty')
12

>>> foo('qwerty')
12

>>> foo('')
24

Pertama, kami memastikan bahwa menggunakan byte masih dimungkinkan. Setelah itu, kami memastikan bahwa sekarang kami dapat meneruskan argumen string ke fungsi foo juga. Perhatikan bahwa fungsi foo untuk argumen string dengan huruf Rusia mengembalikan nilai dua kali lebih besar dari string yang hanya berisi huruf Latin. Ini terjadi karena fungsi foo tidak menghitung panjang string dalam karakter (dan menggandakannya), tetapi panjang array char * , dan karena dalam UTF-8 pengkodean huruf Rusia menempati 2 byte, ukuran array char * setelah konversi dari String python ternyata dua kali lebih panjang.

Baik! Kami memecahkan masalah dengan argumen fungsifoo , tetapi bagaimana jika kita memiliki lusinan atau ratusan fungsi seperti itu di perpustakaan kami, apakah Anda harus menentukan pengkodean parameter untuk masing-masing fungsi? Seringkali pengkodean yang digunakan dalam suatu program adalah sama, dan tidak ada tujuan untuk fungsi yang berbeda untuk menunjukkan pengkodean yang berbeda. Dalam hal ini, dimungkinkan untuk menentukan pengkodean default di SIP, dan jika untuk beberapa fungsi diperlukan pengkodean lainnya, maka itu dapat didefinisikan ulang menggunakan anotasi Pengkodean .

Untuk mengatur pengkodean parameter fungsi secara default, % direktif % DefaultEncoding digunakan . Penggunaannya ditunjukkan dalam contoh yang terletak di folder pyfoo_c_03 .

Untuk memanfaatkan direktif % DefaultEncoding , ubah file pyfoo.sip, sekarang isinya adalah sebagai berikut:

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

int foo(char*);

Sekarang, jika argumennya adalah fungsi dari tipe char , char * , dll. Jika tidak ada penjelasan Enkode , maka enkode diambil dari arahan % DefaultEncoding , dan jika tidak, maka konversi tidak dilakukan, dan untuk semua parameter karakter * , dll. perlu untuk mentransfer bukan garis, tetapi byte .

Contoh dari folder pyfoo_c_03 dikumpulkan dan diverifikasi dengan cara yang sama dengan contoh dari folder pyfoo_c_02 .

Secara singkat tentang project.py. Perakitan otomatis


Sejauh ini, kami telah menggunakan dua file utilitas untuk membuat Python binding - pyproject.toml dan pyfoo.sip . Sekarang kita akan berkenalan dengan file lain seperti itu, yang seharusnya disebut project.py . Dengan skrip ini, kita dapat memengaruhi proses pembuatan paket kita. Mari kita lakukan otomatisasi pembuatan. Untuk mengumpulkan contoh-contoh pyfoo_c_01 - pyfoo_c_03 dari bagian sebelumnya, Anda harus terlebih dahulu pergi ke foo / folder , kompilasi di sana menggunakan perintah make , kembali ke folder tempat file pyproject.toml berada, dan baru kemudian mulai membangun paket menggunakan salah satu dari perintah sip- * .

Sekarang tujuan kami adalah untuk memastikan bahwa ketika menjalankan perintah sip-build , sip-sdist dan sip-wheel , perakitan foo C-library dimulai terlebih dahulu , dan kemudian perintah itu sendiri sudah diluncurkan.

Contoh yang dibuat di bagian ini terletak di folder sumber pyfoo_c_04 .

Untuk mengubah proses pembangunan, kita dapat mendeklarasikan kelas dalam file project.py (nama file seharusnya hanya itu) yang diturunkan dari kelas sipbuild.Project . Kelas ini memiliki metode yang dapat kita atur sendiri. Saat ini kami tertarik dengan metode berikut:

  • membangun . Dipanggil selama panggilan ke perintah sip-build .
  • build_sdist . Disebut ketika perintah sip-sdist dipanggil .
  • build_wheel . Disebut ketika perintah sip-wheel dipanggil .
  • instal . Disebut ketika perintah sip-install dipanggil .

Artinya, kita dapat mendefinisikan kembali perilaku perintah-perintah ini. Sebenarnya, metode yang terdaftar dideklarasikan dalam kelas abstrak sipbuild . Proyek Ekstrak , dari mana kelas turunan sipbuild . Proyek dibuat .

Buat file project.py dengan konten berikut:

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

Kami mendeklarasikan kelas FooProject , yang berasal dari kelas sipbuild.Project, dan mendefinisikan build , build_sdist , build_wheel, dan instal metode di dalamnya . Dalam semua metode ini, kita memanggil metode dengan nama yang sama dari kelas dasar, sebelum memanggil metode _build_foo , yang memulai eksekusi perintah make di folder foo .

Perhatikan bahwa metode build_sdist dan build_wheel harus mengembalikan nama file yang mereka buat. Ini tidak ditulis dalam dokumentasi, tetapi ditunjukkan dalam sumber SIP.

Sekarang kita tidak perlu menjalankan perintah makesecara manual untuk membangun perpustakaan foo , ini akan dilakukan secara otomatis.

Jika Anda sekarang menjalankan perintah sip-wheel di folder pyfoo_c_04 , sebuah file dibuat dengan nama pyfoo-0.4-cp38-cp38-manylinux1_x86_64.whl atau serupa tergantung pada sistem operasi dan versi Python Anda. Paket ini dapat diinstal menggunakan perintah:



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

Setelah itu, Anda dapat memastikan bahwa fungsi foo dari modul foo masih berfungsi.

Tambahkan opsi baris perintah untuk membangun


Contoh berikut ada di folder pyfoo_c_05 , dan paket memiliki nomor versi 0,5 (lihat pengaturan di file pyproject.toml ). Contoh ini didasarkan pada contoh dari dokumentasi dengan beberapa koreksi. Dalam contoh ini, kami akan mengulang file project.py kami dan menambahkan opsi baris perintah baru untuk build.

Dalam contoh kami, kami sedang membangun foo perpustakaan yang sangat sederhana, dan dalam proyek nyata, pustaka bisa sangat besar dan kemudian tidak masuk akal untuk memasukkannya dalam kode sumber proyek pengikatan Python. Biarkan saya mengingatkan Anda bahwa SIP pada awalnya dibuat untuk membuat pengikatan untuk perpustakaan besar seperti Qt. Anda dapat, tentu saja, berpendapat bahwa submodul dari git dapat membantu mengatur sumber, tetapi bukan itu intinya. Misalkan perpustakaan mungkin tidak ada di folder dengan sumber yang mengikat. Dalam kasus ini, muncul pertanyaan, di mana kolektor SIP harus mencari header perpustakaan dan file objek? Dalam hal ini, pengguna yang berbeda mungkin memiliki cara sendiri untuk menempatkan perpustakaan.

Untuk mengatasi masalah ini, kami akan menambahkan dua opsi baris perintah baru ke sistem build, yang dengannya Anda dapat menentukan lintasan ke file foo.h (parameter --foo-include-dir) dan ke file objek perpustakaan (parameter --foo-library-dir ). Selain itu, kami akan menganggap bahwa jika parameter ini tidak ditentukan, maka pustaka foo masih terletak bersama dengan sumber yang mengikat.

Kita perlu membuat file project.py lagi , dan di dalamnya mendeklarasikan kelas yang berasal dari sipbuild.Project . Mari kita lihat versi baru file project.py terlebih dahulu , dan kemudian lihat cara kerjanya.

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)

Kami kembali membuat kelas FooProject , berasal dari sipbuild.Project . Dalam contoh ini, perakitan otomatis perpustakaan foo dinonaktifkan , karena sekarang diasumsikan bahwa ia dapat berada di tempat lain, dan pada saat pengikatan dibuat, file header dan objek harus siap. Tiga metode yang didefinisikan ulang

di FooProject kelas : get_options , apply_user_defaults dan pembaruan . Pertimbangkan mereka dengan lebih cermat.

Mari kita mulai dengan metode get_options . Metode ini harus mengembalikan daftar instance dari kelas sipbuild.Option. Setiap item daftar adalah opsi baris perintah. Di dalam metode overridden, kita mendapatkan daftar opsi default (variabel opsi ) dengan memanggil metode kelas dasar dari nama yang sama, kemudian membuat dua opsi baru ( --foo_include_dir dan --foo_library_dir ) dan menambahkannya ke daftar, dan kemudian mengembalikan daftar ini dari fungsi.

Konstruktor dari kelas Opsi menerima satu parameter yang diperlukan (nama opsi) dan sejumlah besar yang opsional yang menggambarkan jenis nilai untuk parameter ini, nilai default, deskripsi parameter, dan beberapa lainnya. Contoh ini menggunakan opsi berikut untuk konstruktor Opsi :

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

Metode apply_user_defaults kelebihan beban berikut dirancang untuk menetapkan nilai parameter yang dapat dilewati pengguna melalui baris perintah. Metode apply_user_defaults dari kelas dasar membuat variabel (anggota kelas) untuk setiap parameter baris perintah yang dibuat dalam metode get_options , jadi penting untuk memanggil metode kelas dasar dari nama yang sama sebelum menggunakan variabel yang dibuat sehingga semua variabel yang dibuat menggunakan parameter baris perintah dibuat dan diinisialisasi dengan nilai default . Setelah itu, dalam contoh kita, variabel self.foo_include_dir dan self.foo_library_dir akan dibuat. Jika pengguna belum menentukan parameter baris perintah yang sesuai, maka mereka akan mengambil nilai default sesuai dengan parameter konstruktor dari kelas Opsi (parameter default ). Jika parameter default tidak disetel, maka tergantung pada jenis nilai parameter yang diharapkan, itu akan diinisialisasi baik Tidak ada, atau daftar kosong, atau 0.

Di dalam metode apply_user_defaults , kami memastikan bahwa path dalam variabel self.foo_include_dir dan self.foo_library_dir selalu absolut. Ini diperlukan agar tidak tergantung pada apa folder yang akan bekerja pada saat perakitan dimulai.

Metode kelebihan beban terakhir di kelas ini adalah pembaruan.. Metode ini dipanggil ketika perlu untuk menerapkan perubahan yang dibuat sebelum proyek ke proyek ini. Misalnya, ubah atau tambahkan parameter yang ditentukan dalam file pyproject.toml . Dalam contoh sebelumnya, kami menetapkan path ke file header dan objek menggunakan parameter include-dirs dan library-dirs , masing-masing, di dalam bagian [tool.sip.bindings.pyfoo] . Sekarang kita akan mengatur parameter ini dari skrip project.py , jadi pada file pyproject.toml kita akan menghapus parameter ini:

[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"]

Dalam metode memperbarui kita dari kamus self.bindings mengetik pyfoo gat misalnya sipbuild.Bindings . Nama kuncinya sesuai dengan [tool.sip.bindings. pyfoo ] dari file pyproject.toml , dan instance kelas yang diperoleh menjelaskan pengaturan yang dijelaskan dalam bagian ini. Kemudian, anggota kelas ini include_dirs dan library_dirs (nama-nama anggota sesuai dengan parameter include-dirs dan library-dirs dengan tanda hubung diganti dengan garis bawah) ditugaskan daftar berisi jalur yang disimpan di anggota self.foo_include_dir dan self.foo_library_dir. Dalam contoh ini, untuk akurasi, kami memeriksa bahwa nilai self.foo_include_dir dan self.foo_library_dir tidak sama dengan Tidak ada , tetapi dalam contoh ini kondisi ini selalu terpenuhi karena parameter baris perintah yang kami buat memiliki nilai default.

Dengan demikian, kami menyiapkan file konfigurasi sehingga selama perakitan itu mungkin untuk menunjukkan path ke file header dan objek. Mari kita periksa apa yang terjadi.

Pertama, pastikan bahwa nilai default berfungsi. Untuk melakukan ini, buka folder pyfoo_c_05 / foo dan bangun pustaka menggunakan perintah make , karena kami menonaktifkan pustaka otomatis dalam contoh ini.

Setelah itu, buka folderpyfoo_c_05 dan jalankan perintah sip-wheel . Sebagai hasil dari perintah ini, file pyfoo-0.5-cp38-cp38-manylinux1_x86_64.whl atau dengan nama yang sama akan dibuat .

Sekarang pindahkan folder foo di suatu tempat di luar folder pyfoo_c_05 dan jalankan perintah sip-wheel lagi . Akibatnya, kami mendapatkan kesalahan yang diharapkan dengan menginformasikan bahwa kami tidak memiliki file perpustakaan objek:

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

Setelah itu, jalankan sip-wheel menggunakan opsi baris perintah baru:

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

Alih-alih elipsis, Anda harus menentukan path ke folder tempat Anda memindahkan folder foo dengan pustaka yang dirakit. Akibatnya, majelis harus berhasil membuat file .whl. Modul yang dibuat dapat diinstal dan diuji dengan cara yang sama seperti pada bagian sebelumnya.

Periksa urutan metode panggilan dari project.py


Contoh berikutnya, yang akan kami pertimbangkan, akan sangat sederhana, itu akan menunjukkan urutan pemanggilan metode kelas Proyek , yang kami muat berlebihan di bagian sebelumnya. Ini dapat berguna untuk memahami kapan variabel dapat diinisialisasi. Contoh ini terletak di folder pyfoo_c_06 di repositori sumber.

Inti dari contoh ini adalah membebani semua metode yang kami gunakan sebelumnya di kelas FooProject , yang terletak di file project.py , dan menambahkan panggilan ke fungsi cetak , yang akan menampilkan nama metode di mana ia berada:

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

Pembaca yang penuh perhatian harus mencatat bahwa selain metode yang digunakan sebelumnya, metode apply_nonuser_defaults () , yang belum kita bicarakan sebelumnya , kelebihan beban dalam contoh ini . Metode ini merekomendasikan pengaturan nilai default untuk semua variabel yang tidak dapat diubah melalui parameter baris perintah.

Dalam file pyproject.toml, kembalikan jalur eksplisit ke perpustakaan:

[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"]

Agar proyek berhasil dibangun , Anda harus masuk ke folder foo dan membangun perpustakaan di sana menggunakan perintah make . Setelah itu, kembali ke folder pyfoo_c_06 dan jalankan, misalnya, perintah sip-wheel . Akibatnya, jika Anda membuang peringatan kompiler, teks berikut akan ditampilkan:

get_options ()
apply_nonuser_defaults ()
get_options ()
get_options ()
get_opefe ()
get_options ()
pembaruan ()
binding ini akan dibuat: pyfoo.
build_wheel ()
Menghasilkan binding pyfoo ...
Mengkompilasi modul 'foo' ...
Roda telah dibuat.

Garis tebal ditampilkan yang merupakan output dari file project.py kami . Jadi, kita melihat bahwa metode get_options dipanggil beberapa kali, dan ini harus diperhitungkan jika Anda akan menginisialisasi variabel anggota apa pun di kelas yang berasal dari Project . Metode get_options bukan tempat terbaik untuk ini.

Juga berguna untuk mengingat bahwa metode apply_nonuser_defaults dipanggil sebelum metode apply_user_defaults , yaitu dalam metode apply_user_defaults sudah dimungkinkan untuk menggunakan variabel yang nilainya diatur dalam metode apply_nonuser_defaults .

Setelah itu, metode pembaruan dipanggil, dan pada akhirnya, metode yang bertanggung jawab langsung untuk perakitan, dalam kasus kami, build_wheel .

Kesimpulan ke bagian pertama


Pada artikel ini, kami mulai mempelajari alat SIP yang dirancang untuk membuat binding Python untuk perpustakaan yang ditulis dalam C atau C ++. Pada bagian pertama artikel ini, kami memeriksa dasar-dasar menggunakan SIP menggunakan contoh membuat pengikatan Python untuk pustaka yang sangat sederhana yang ditulis dalam C.

Kami menemukan file yang Anda perlu buat untuk bekerja dengan SIP. File pyproject.toml berisi informasi tentang paket (nama, nomor versi, lisensi, dan jalur ke file header dan objek). Menggunakan file project.py , Anda bisa memengaruhi proses pembuatan paket Python, misalnya, mulai membangun C-library atau membiarkan pengguna menentukan lokasi header dan file objek perpustakaan.

Dalam file * .sipmenjelaskan antarmuka modul Python yang mencantumkan fungsi dan kelas yang akan dimuat dalam modul. Arahan dan anotasi digunakan untuk menggambarkan antarmuka dalam file * .sip .

Pada bagian kedua artikel ini, kita akan membuat pengikatan atas pustaka berorientasi objek yang ditulis dalam C ++, dan dengan contohnya kita akan mempelajari teknik yang akan berguna dalam menggambarkan antarmuka kelas C ++, dan pada saat yang sama kita akan membahas arahan dan anotasi baru untuk kita.

Bersambung.

Referensi



All Articles