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

Im ersten Teil des Artikels haben wir die Grundlagen der Arbeit mit dem SIP-Dienstprogramm zum Erstellen von Python-Bindungen fĂŒr in C und C ++ geschriebene Bibliotheken untersucht. Wir haben uns die Basisdateien angesehen, die Sie fĂŒr die Arbeit mit SIP erstellen mĂŒssen, und haben uns mit Direktiven und Anmerkungen befasst. Bisher haben wir die Bindung fĂŒr eine in C geschriebene einfache Bibliothek durchgefĂŒhrt. In diesem Teil werden wir herausfinden, wie die Bindung fĂŒr eine C ++ - Bibliothek durchgefĂŒhrt wird, die Klassen enthĂ€lt. Am Beispiel dieser Bibliothek werden wir sehen, welche Techniken bei der Arbeit mit einer objektorientierten Bibliothek nĂŒtzlich sein können, und gleichzeitig werden wir uns mit neuen Anweisungen und Anmerkungen fĂŒr uns befassen.

Alle Beispiele fĂŒr den Artikel finden Sie im Github-Repository unter: https://github.com/Jenyay/sip-examples .

Erstellen einer Bindung fĂŒr eine Bibliothek in C ++


Das folgende Beispiel, das wir betrachten werden, befindet sich im Ordner pyfoo_cpp_01 .

Erstellen Sie zunĂ€chst eine Bibliothek, fĂŒr die wir die Bindung durchfĂŒhren. Die Bibliothek befindet sich weiterhin im Ordner foo und enthĂ€lt eine Klasse - Foo . Die Header-Datei foo.h mit der Deklaration dieser Klasse lautet wie folgt:

#ifndef FOO_LIB
#define FOO_LIB

class Foo {
    private:
        int _int_val;
        char* _string_val;
    public:
        Foo(int int_val, const char* string_val);
        virtual ~Foo();

        void set_int_val(int val);
        int get_int_val();

        void set_string_val(const char* val);
        char* get_string_val();
};

#endif

Dies ist eine einfache Klasse mit zwei Gettern und Setzern, die Werte vom Typ int und char * setzt und zurĂŒckgibt . Die Implementierung der Klasse ist wie folgt:

#include <string.h>

#include "foo.h"

Foo::Foo(int int_val, const char* string_val): _int_val(int_val) {
    _string_val = nullptr;
    set_string_val(string_val);
}

Foo::~Foo(){
    delete[] _string_val;
    _string_val = nullptr;
}

void Foo::set_int_val(int val) {
    _int_val = val;
}

int Foo::get_int_val() {
    return _int_val;
}

void Foo::set_string_val(const char* val) {
    if (_string_val != nullptr) {
        delete[] _string_val;
    }

    auto count = strlen(val) + 1;
    _string_val = new char[count];
    strcpy(_string_val, val);
}

char* Foo::get_string_val() {
    return _string_val;
}

Um die FunktionalitÀt der Bibliothek zu testen, enthÀlt der Ordner foo auch die Datei main.cpp mit der Klasse Foo :

#include <iostream>

#include "foo.h"

using std::cout;
using std::endl;

int main(int argc, char* argv[]) {
    auto foo = Foo(10, "Hello");
    cout << "int_val: " << foo.get_int_val() << endl;
    cout << "string_val: " << foo.get_string_val() << endl;

    foo.set_int_val(0);
    foo.set_string_val("Hello world!");

    cout << "int_val: " << foo.get_int_val() << endl;
    cout << "string_val: " << foo.get_string_val() << endl;
}

Verwenden Sie zum Erstellen der foo- Bibliothek das folgende Makefile :

CC=g++
CFLAGS=-c -fPIC
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.cpp
    $(CC) $(CFLAGS) main.cpp -o $(DIR_OUT)/main.o

libfoo.a: makedir foo.cpp
    $(CC) $(CFLAGS) foo.cpp -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)/*

Der Unterschied zum Makefile in den vorherigen Beispielen besteht neben der Änderung des Compilers von gcc in g ++ darin , dass eine weitere Option -fPIC zum Kompilieren hinzugefĂŒgt wurde , die den Compiler anweist , den Code auf eine bestimmte Weise in die Bibliothek zu stellen (den sogenannten "positionsunabhĂ€ngigen Code"). Da es in diesem Artikel nicht um Compiler geht, werden wir nicht genauer untersuchen, was dieser Parameter bewirkt und warum er benötigt wird.

Beginnen wir mit der Bindung fĂŒr diese Bibliothek. Die Dateien pyproject.toml und project.py sind gegenĂŒber den vorherigen Beispielen nahezu unverĂ€ndert . So sieht die Datei pyproject.toml jetzt aus :

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

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

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

Jetzt werden unsere in C ++ geschriebenen Beispiele in das Python-Paket pyfoocpp gepackt . Dies ist möglicherweise die einzige erkennbare Änderung in dieser Datei.

Die Datei project.py bleibt dieselbe wie im Beispiel pyfoo_c_04 :

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

Und hier werden wir die Datei pyfoocpp.sip genauer betrachten. Ich möchte Sie daran erinnern, dass diese Datei die Schnittstelle fĂŒr das zukĂŒnftige Python-Modul beschreibt: Was sollte sie enthalten, wie sollte die Klassenschnittstelle aussehen usw. Die .sip-Datei ist nicht erforderlich, um die Bibliotheks-Header-Datei zu wiederholen, obwohl sie viele Gemeinsamkeiten haben. Innerhalb dieser Klasse können neue Methoden hinzugefĂŒgt werden, die nicht in der ursprĂŒnglichen Klasse enthalten waren. Jene. Die in der .sip-Datei beschriebene Schnittstelle kann Bibliotheksklassen bei Bedarf an die in Python akzeptierten Prinzipien anpassen. In der Datei pyfoocpp.sip sehen wir neue Anweisungen fĂŒr uns.

Lassen Sie uns zunÀchst sehen, was diese Datei enthÀlt:

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

class Foo {
    %TypeHeaderCode
    #include <foo.h>
    %End

    public:
        Foo(int, const char*);

        void set_int_val(int);
        int get_int_val();

        void set_string_val(const char*);
        char* get_string_val();
};

Die ersten Zeilen sollten uns bereits aus den vorhergehenden Beispielen klar sein. In der % Module- Direktive geben wir den Namen des Python-Moduls an, das erstellt werden soll ( dh um dieses Modul zu verwenden, mĂŒssen wir die Befehle import foocpp oder from foocpp import ... verwenden . In derselben Direktive geben wir an, dass wir jetzt die Sprache haben - C ++. Die Direktive% DefaultEncoding legt die Codierung fest, mit der die Python-Zeichenfolge in die Typen char , const char , char * und const char * konvertiert wird .

Anschließend folgt die Schnittstellendeklaration der Foo- Klasse . Unmittelbar nach der Deklaration der Foo- KlasseDie Direktive % TypeHeaderCode , die mit der Direktive % End endet, wird immer noch nicht verwendet . Die Direktive% TypeHeaderCode muss Code enthalten, der die Schnittstelle der C ++ - Klasse deklariert, fĂŒr die der Wrapper erstellt wird. In dieser Anweisung reicht es in der Regel aus, die Header-Datei in die Klassendeklaration aufzunehmen.

Danach werden die Klassenmethoden aufgelistet, die in die Methoden der Foo- Klasse fĂŒr die Python-Sprache konvertiert werden . Es ist wichtig zu beachten, dass wir zu diesem Zeitpunkt nur öffentliche Methoden deklarieren, auf die ĂŒber die Foo- Klasse in Python zugegriffen werden kann (da es in Python keine privaten und geschĂŒtzten Mitglieder gibt). Da wir am Anfang die Direktive % DefaultEncoding verwendet habenIn Methoden, die Argumente vom Typ const char * annehmen , können Sie die Annotation Encoding nicht verwenden , um die Codierung fĂŒr die Konvertierung dieser Parameter in Python-Zeichenfolgen anzugeben und umgekehrt.

Jetzt mĂŒssen wir nur noch das pyfoocpp Python-Paket kompilieren und testen. Bevor wir jedoch ein vollwertiges Radpaket zusammenstellen, verwenden wir den Befehl sip-build und sehen, welche Quelldateien SIP fĂŒr die spĂ€tere Kompilierung erstellt, und versuchen, darin etwas zu finden, das der Klasse Ă€hnelt, die in Python-Code erstellt wird. Dazu muss der obige Befehl sip-build im Ordner pyfoo_cpp_01 aufgerufen werden . Infolgedessen wird der Build- Ordner erstellt. mit folgenden Inhalten:

bauen
└── foocpp
    ├── apiversions.c
    ├── array.c
    ├── array.h
    ├── bool.cpp
    ├── build
    │   └── temp.linux-x86_64-3.8
    │       ├── apiversions.o
    │       ├── array.o
    │       ├── bool.o
    │       ├── descriptors.o
    │       ├── int_convertors.o
    │       ├── objmap.o
    │       ├── qtlib.o
    │       ├── sipfoocppcmodule.o
    │       ├── sipfoocppFoo.o
    │       ├── siplib.o
    │       ├── threads.o
    │       └── voidptr.o
    ├── descriptors.c
    ├── foocpp.cpython-38-x86_64-linux-gnu.so
    ├── int_convertors.c
    ├── objmap.c
    ├── qtlib.c
    ├── sipAPIfoocpp.h
    ├── sipfoocppcmodule.cpp
    ├── sipfoocppFoo.cpp
    ├── sip.h
    ├── sipint.h
    ├── siplib.c
    ├── threads.c
    └── voidptr.c


Betrachten Sie als zusÀtzliche Aufgabe die Datei sipfoocppFoo.cpp sorgfÀltig (wir werden sie in diesem Artikel nicht im Detail behandeln):

/*
 * Interface wrapper code.
 *
 * Generated by SIP 5.1.1
 */

#include "sipAPIfoocpp.h"

#line 6 "/home/jenyay/temp/2/pyfoocpp.sip"
    #include <foo.h>
#line 12 "/home/jenyay/temp/2/build/foocpp/sipfoocppFoo.cpp"

PyDoc_STRVAR(doc_Foo_set_int_val, "set_int_val(self, int)");

extern "C" {static PyObject *meth_Foo_set_int_val(PyObject *, PyObject *);}
static PyObject *meth_Foo_set_int_val(PyObject *sipSelf, PyObject *sipArgs)
{
    PyObject *sipParseErr = SIP_NULLPTR;

    {
        int a0;
         ::Foo *sipCpp;

        if (sipParseArgs(&sipParseErr, sipArgs, "Bi", &sipSelf, sipType_Foo, &sipCpp, &a0))
        {
            sipCpp->set_int_val(a0);

            Py_INCREF(Py_None);
            return Py_None;
        }
    }

    /* Raise an exception if the arguments couldn't be parsed. */
    sipNoMethod(sipParseErr, sipName_Foo, sipName_set_int_val, doc_Foo_set_int_val);

    return SIP_NULLPTR;
}

PyDoc_STRVAR(doc_Foo_get_int_val, "get_int_val(self) -> int");

extern "C" {static PyObject *meth_Foo_get_int_val(PyObject *, PyObject *);}
static PyObject *meth_Foo_get_int_val(PyObject *sipSelf, PyObject *sipArgs)
{
    PyObject *sipParseErr = SIP_NULLPTR;

    {
         ::Foo *sipCpp;

        if (sipParseArgs(&sipParseErr, sipArgs, "B", &sipSelf, sipType_Foo, &sipCpp))
        {
            int sipRes;

            sipRes = sipCpp->get_int_val();

            return PyLong_FromLong(sipRes);
        }
    }

    /* Raise an exception if the arguments couldn't be parsed. */
    sipNoMethod(sipParseErr, sipName_Foo, sipName_get_int_val, doc_Foo_get_int_val);

    return SIP_NULLPTR;
}

PyDoc_STRVAR(doc_Foo_set_string_val, "set_string_val(self, str)");

extern "C" {static PyObject *meth_Foo_set_string_val(PyObject *, PyObject *);}
static PyObject *meth_Foo_set_string_val(PyObject *sipSelf, PyObject *sipArgs)
{
    PyObject *sipParseErr = SIP_NULLPTR;

    {
        const char* a0;
        PyObject *a0Keep;
         ::Foo *sipCpp;

        if (sipParseArgs(&sipParseErr, sipArgs, "BA8", &sipSelf, sipType_Foo, &sipCpp, &a0Keep, &a0))
        {
            sipCpp->set_string_val(a0);
            Py_DECREF(a0Keep);

            Py_INCREF(Py_None);
            return Py_None;
        }
    }

    /* Raise an exception if the arguments couldn't be parsed. */
    sipNoMethod(sipParseErr, sipName_Foo, sipName_set_string_val, doc_Foo_set_string_val);

    return SIP_NULLPTR;
}

PyDoc_STRVAR(doc_Foo_get_string_val, "get_string_val(self) -> str");

extern "C" {static PyObject *meth_Foo_get_string_val(PyObject *, PyObject *);}
static PyObject *meth_Foo_get_string_val(PyObject *sipSelf, PyObject *sipArgs)
{
    PyObject *sipParseErr = SIP_NULLPTR;

    {
         ::Foo *sipCpp;

        if (sipParseArgs(&sipParseErr, sipArgs, "B", &sipSelf, sipType_Foo, &sipCpp))
        {
            char*sipRes;

            sipRes = sipCpp->get_string_val();

            if (sipRes == SIP_NULLPTR)
            {
                Py_INCREF(Py_None);
                return Py_None;
            }

            return PyUnicode_FromString(sipRes);
        }
    }

    /* Raise an exception if the arguments couldn't be parsed. */
    sipNoMethod(sipParseErr, sipName_Foo, sipName_get_string_val, doc_Foo_get_string_val);

    return SIP_NULLPTR;
}

/* Call the instance's destructor. */
extern "C" {static void release_Foo(void *, int);}
static void release_Foo(void *sipCppV, int)
{
    delete reinterpret_cast< ::Foo *>(sipCppV);
}

extern "C" {static void dealloc_Foo(sipSimpleWrapper *);}
static void dealloc_Foo(sipSimpleWrapper *sipSelf)
{
    if (sipIsOwnedByPython(sipSelf))
    {
        release_Foo(sipGetAddress(sipSelf), 0);
    }
}

extern "C" {static void *init_type_Foo(sipSimpleWrapper *, PyObject *, 
                 PyObject *, PyObject **, PyObject **, PyObject **);}
static void *init_type_Foo(sipSimpleWrapper *, PyObject *sipArgs, PyObject *sipKwds,
                                   PyObject **sipUnused, PyObject **, PyObject **sipParseErr)
{
     ::Foo *sipCpp = SIP_NULLPTR;

    {
        int a0;
        const char* a1;
        PyObject *a1Keep;

        if (sipParseKwdArgs(sipParseErr, sipArgs, sipKwds, SIP_NULLPTR, sipUnused, "iA8", &a0, &a1Keep, &a1))
        {
            sipCpp = new  ::Foo(a0,a1);
            Py_DECREF(a1Keep);

            return sipCpp;
        }
    }

    {
        const  ::Foo* a0;

        if (sipParseKwdArgs(sipParseErr, sipArgs, sipKwds, SIP_NULLPTR, sipUnused, "J9", sipType_Foo, &a0))
        {
            sipCpp = new  ::Foo(*a0);

            return sipCpp;
        }
    }

    return SIP_NULLPTR;
}

static PyMethodDef methods_Foo[] = {
    {sipName_get_int_val, meth_Foo_get_int_val, METH_VARARGS, doc_Foo_get_int_val},
    {sipName_get_string_val, meth_Foo_get_string_val, METH_VARARGS, doc_Foo_get_string_val},
    {sipName_set_int_val, meth_Foo_set_int_val, METH_VARARGS, doc_Foo_set_int_val},
    {sipName_set_string_val, meth_Foo_set_string_val, METH_VARARGS, doc_Foo_set_string_val}
};

PyDoc_STRVAR(doc_Foo, "\1Foo(int, str)\n"
"Foo(Foo)");

sipClassTypeDef sipTypeDef_foocpp_Foo = {
    {
        -1,
        SIP_NULLPTR,
        SIP_NULLPTR,
        SIP_TYPE_CLASS,
        sipNameNr_Foo,
        SIP_NULLPTR,
        SIP_NULLPTR
    },
    {
        sipNameNr_Foo,
        {0, 0, 1},
        4, methods_Foo,
        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},
    },
    doc_Foo,
    -1,
    -1,
    SIP_NULLPTR,
    SIP_NULLPTR,
    init_type_Foo,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    dealloc_Foo,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    release_Foo,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR,
    SIP_NULLPTR
};

Erstellen Sie nun das Paket mit dem Befehl sip-whe . Wenn nach AusfĂŒhrung dieses Befehls alles gut geht, wird eine pyfoocpp-0.1-cp38-cp38-manylinux1_x86_64.whl-Datei oder mit einem Ă€hnlichen Namen erstellt . Installieren Sie es mit dem Befehl pip install --user pyfoocpp-0.1-cp38-cp38-manylinux1_x86_64.whl und fĂŒhren Sie den Python-Interpreter aus, um Folgendes zu ĂŒberprĂŒfen:

>>> from foocpp import Foo
>>> x = Foo(10, 'Hello')

>>> x.get_int_val()
10

>>> x.get_string_val()
'Hello'

>>> x.set_int_val(50)
>>> x.set_string_val('')

>>> x.get_int_val()
50

>>> x.get_string_val()
''

Funktioniert! Daher haben wir gerade ein Python-Modul mit einer Bindung fĂŒr eine Klasse in C ++ erstellt. Weiter werden wir Schönheit in diese Klasse bringen und verschiedene Annehmlichkeiten hinzufĂŒgen.

Eigenschaften hinzufĂŒgen


Mit SIP erstellte Klassen mĂŒssen die C ++ - Klassenschnittstelle nicht genau wiederholen. In unserer Foo- Klasse gibt es beispielsweise zwei Getter und zwei Setter, die eindeutig zu einer Eigenschaft kombiniert werden können, um die Klasse „Python“ zu machen. Das HinzufĂŒgen von Eigenschaften mit sip ist einfach genug, wie ein Beispiel im Ordner pyfoo_cpp_02 zeigt .

Dieses Beispiel Ă€hnelt dem vorherigen, der Hauptunterschied liegt in der Datei pyfoocpp.sip , die jetzt folgendermaßen aussieht:

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

class Foo {
    %TypeHeaderCode
    #include <foo.h>
    %End

    public:
        Foo(int, const char*);

        void set_int_val(int);
        int get_int_val();
        %Property(name=int_val, get=get_int_val, set=set_int_val)

        void set_string_val(const char*);
        char* get_string_val();
        %Property(name=string_val, get=get_string_val, set=set_string_val)
};

Wie Sie sehen können, ist alles ganz einfach. Zum HinzufĂŒgen einer Eigenschaft ist die Direktive % Property vorgesehen , fĂŒr die zwei Parameter erforderlich sind: name , um den Namen der Eigenschaft anzugeben , und get , um eine Methode anzugeben, die einen Wert zurĂŒckgibt (getter). Möglicherweise gibt es keinen Setter, aber wenn der Eigenschaft auch Werte zugewiesen werden mĂŒssen, wird die Setter-Methode als Wert des Set- Parameters angegeben . In unserem Beispiel werden Eigenschaften auf relativ einfache Weise erstellt, da es bereits Funktionen gibt, die als Getter und Setter fungieren.

Wir können das Paket nur mit dem Befehl sip-Wheel sammeln und installieren. Danach ĂŒberprĂŒfen wir die Funktion der Eigenschaften im Python-Interpreter-Befehlsmodus:

>>> from foocpp import Foo
>>> x = Foo(10, "Hello")
>>> x.int_val
10

>>> x.string_val
'Hello'

>>> x.int_val = 50
>>> x.string_val = ''

>>> x.get_int_val()
50

>>> x.get_string_val()
''

Wie Sie am Beispiel der Verwendung der Foo- Klasse sehen können , funktionieren die Eigenschaften int_val und string_val sowohl zum Lesen als auch zum Schreiben.

Dokumentationszeilen hinzufĂŒgen


Wir werden unsere Foo- Klasse weiter verbessern . Das folgende Beispiel im Ordner pyfoo_cpp_03 zeigt, wie Sie Dokumentationszeilen (docstring) zu verschiedenen Elementen einer Klasse hinzufĂŒgen. Dieses Beispiel basiert auf dem vorherigen und die HauptĂ€nderung betrifft die Datei pyfoocpp.sip . Hier sind die Inhalte:

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

class Foo {
%Docstring
Class example from C++ library
%End

    %TypeHeaderCode
    #include <foo.h>
    %End

    public:
        Foo(int, const char*);

        void set_int_val(int);
        %Docstring(format="deindented", signature="prepended")
            Set integer value
        %End

        int get_int_val();
        %Docstring(format="deindented", signature="prepended")
            Return integer value
        %End

        %Property(name=int_val, get=get_int_val, set=set_int_val)
        {
            %Docstring "deindented"
                The property for integer value
            %End
        };

        void set_string_val(const char*);
        %Docstring(format="deindented", signature="appended")
            Set string value
        %End

        char* get_string_val();
        %Docstring(format="deindented", signature="appended")
            Return string value
        %End

        %Property(name=string_val, get=get_string_val, set=set_string_val)
        {
            %Docstring "deindented"
                The property for string value
            %End
        };
};

Wie Sie bereits verstanden haben, mĂŒssen Sie zum HinzufĂŒgen von Dokumentationszeilen zu einem Element der Klasse die Direktive % Docstring verwenden . Dieses Beispiel zeigt verschiedene Möglichkeiten zur Verwendung dieser Direktive. Um dieses Beispiel besser zu verstehen, kompilieren wir das pyfoocpp- Paket sofort mit dem Befehl sip- whe , installieren es und behandeln, welche Parameter dieser Direktive sich auf was auswirken , unter BerĂŒcksichtigung der resultierenden Dokumentationszeilen im Python-Befehlsmodus. Ich möchte Sie daran erinnern, dass Dokumentationszeilen als Mitglieder der __doc__- Objekte gespeichert sind, zu denen diese Zeilen gehören.

Die erste Dokumentationszeile bezieht sich auf die Foo- Klasse.. Wie Sie sehen können, befinden sich alle Dokumentationszeilen zwischen den Anweisungen% Docstring und % End . In den Zeilen 5 bis 7 dieses Beispiels werden keine zusĂ€tzlichen Parameter der % Docstring- Direktive verwendet , sodass die Dokumentationszeile unverĂ€ndert in die Foo- Klasse geschrieben wird. Aus diesem Grund gibt es in den Zeilen 5 bis 7 keine EinrĂŒckungen, da sonst auch EinrĂŒckungen vor der Dokumentationszeile in Foo .__ doc__ fallen wĂŒrden. Wir werden sicherstellen, dass die Foo- Klasse wirklich die von uns eingefĂŒhrte Dokumentationszeile enthĂ€lt:

>>> from foocpp import Foo
>>> Foo.__doc__
'Class example from C++ library'

Die folgende % Docstring- Direktive in den Zeilen 17-19 verwendet zwei Parameter gleichzeitig. Der Formatparameter kann einen von zwei Werten annehmen: "raw" oder "deindented". Im ersten Fall werden die Dokumentationszeilen beim Schreiben gespeichert, und im zweiten Fall werden die anfĂ€nglichen Leerzeichen (jedoch keine Tabulatoren) gelöscht. Der Standardwert fĂŒr den Fall, dass der Formatparameter nicht angegeben wird, kann mit der Direktive % DefaultDocstringFormat festgelegt werden (wir werden ihn etwas spĂ€ter betrachten). Wenn er nicht angegeben wird, wird angenommen, dass format = "raw" ist .

ZusĂ€tzlich zu den angegebenen Dokumentationszeilen fĂŒgt SIP den Dokumentationszeilen eine Beschreibung seiner Signatur hinzu (welche Arten von Variablen werden an der Eingabe erwartet und welche Art die Funktion zurĂŒckgibt). Der Signaturparameter gibt an, wo eine solche Signatur abgelegt werden soll: vor der angegebenen Dokumentationszeile ( Signatur = "vorangestellt" ), danach ( Signatur = "angehĂ€ngt" ) oder nicht die Signatur hinzufĂŒgen ( Signatur = "verworfen" ).

In unserem Beispiel wird der Parameter signatur = "vorangestellt" fĂŒr die Funktionen get_int_val und set_int_val sowie die Signatur = "angehĂ€ngt" fĂŒr die Funktionen get_string_val und set_string_val festgelegt. Der Parameter format = "deindented" wurde ebenfalls hinzugefĂŒgt , um Leerzeichen am Anfang der Dokumentationszeile zu entfernen. Lassen Sie uns ĂŒberprĂŒfen, wie diese Parameter in Python funktionieren:

>>> Foo.get_int_val.__doc__
'get_int_val(self) -> int\nReturn integer value'

>>> Foo.set_int_val.__doc__
'set_int_val(self, int)\nSet integer value'

>>> Foo.get_string_val.__doc__
'Return string value\nget_string_val(self) -> str'

>>> Foo.set_string_val.__doc__
'Set string value\nset_string_val(self, str)'

Wie Sie sehen können, können Sie mithilfe des Signaturparameters der Direktive % Docstring die Position der Funktionssignaturbeschreibung in der Dokumentationszeile Àndern.

FĂŒgen Sie nun eine Dokumentationszeile zu den Eigenschaften hinzu. Beachten Sie, dass in diesem Fall die Direktiven % Docstring ... % End in Klammern nach der Direktive% Property stehen. Dieses Aufzeichnungsformat wird in der Dokumentation zur % Property- Direktive beschrieben . Beachten Sie

auch, wie wir den Direktivenparameter % Docstring angeben . Ein solches Format zum Schreiben von Anweisungen ist möglich, wenn wir nur den ersten Parameter der Anweisung festlegen (in diesem Fall den Formatparameter) Daher werden in diesem Beispiel drei Methoden zur Verwendung von Direktiven gleichzeitig verwendet.

Stellen Sie sicher, dass die Dokumentationszeile fĂŒr die Eigenschaften festgelegt ist:

>>> Foo.int_val.__doc__
'The property for integer value'

>>> Foo.string_val.__doc__
'The property for string value'

>>> help(Foo)
Help on class Foo in module foocpp:

class Foo(sip.wrapper)
 |  Class example from C++ library
 |  
 |  Method resolution order:
 |      Foo
 |      sip.wrapper
 |      sip.simplewrapper
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  get_int_val(...)
 |      get_int_val(self) -> int
 |      Return integer value
 |  
 |  get_string_val(...)
 |      Return string value
 |      get_string_val(self) -> str
 |  
 |  set_int_val(...)
 |      set_int_val(self, int)
 |      Set integer value
 |  
 |  set_string_val(...)
 |      Set string value
 |      set_string_val(self, str)
...


Vereinfachen Sie dieses Beispiel, indem Sie die Standardwerte fĂŒr die Format- und Signaturparameter mithilfe der Anweisungen% DefaultDocstringFormat und % DefaultDocstringSignature festlegen . Die Verwendung dieser Anweisungen wird im Beispiel aus dem Ordner pyfoo_cpp_04 gezeigt . Die Datei pyfoocpp.sip in diesem Beispiel enthĂ€lt den folgenden Code:

%Module(name=foocpp, language="C++")
%DefaultEncoding "UTF-8"
%DefaultDocstringFormat "deindented"
%DefaultDocstringSignature "prepended"

class Foo {
    %Docstring
    Class example from C++ library
    %End

    %TypeHeaderCode
    #include <foo.h>
    %End

    public:
        Foo(int, const char*);

        void set_int_val(int);
        %Docstring
            Set integer value
        %End

        int get_int_val();
        %Docstring
            Return integer value
        %End

        %Property(name=int_val, get=get_int_val, set=set_int_val)
        {
            %Docstring
                The property for integer value
            %End
        };

        void set_string_val(const char*);
        %Docstring
            Set string value
        %End

        char* get_string_val();
        %Docstring
            Return string value
        %End

        %Property(name=string_val, get=get_string_val, set=set_string_val)
        {
            %Docstring
                The property for string value
            %End
        };
};

Am Anfang der Datei wurden die Zeilen % DefaultDocstringFormat "deindented" und % DefaultDocstringSignature "prepended" hinzugefĂŒgt , und dann wurden alle Parameter aus der Direktive % Docstring entfernt.

Nach dem Zusammenstellen und Installieren dieses Beispiels können wir sehen, wie die Beschreibung der Foo- Klasse jetzt aussieht , die im Befehl help (Foo) angezeigt wird :

>>> from foocpp import Foo
>>> help(Foo)

class Foo(sip.wrapper)
 |  Class example from C++ library
 |  
 |  Method resolution order:
 |      Foo
 |      sip.wrapper
 |      sip.simplewrapper
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  get_int_val(...)
 |      get_int_val(self) -> int
 |      Return integer value
 |  
 |  get_string_val(...)
 |      get_string_val(self) -> str
 |      Return string value
 |  
 |  set_int_val(...)
 |      set_int_val(self, int)
 |      Set integer value
 |  
 |  set_string_val(...)
 |      set_string_val(self, str)
 |      Set string value
...

Alles sieht ganz ordentlich und vom gleichen Typ aus.

Benennen Sie Klassen und Methoden um


Wie bereits erwĂ€hnt, muss die von den Python-Bindungen bereitgestellte Schnittstelle nicht mit der von der C / C ++ - Bibliothek bereitgestellten Schnittstelle ĂŒbereinstimmen. Wir haben den oben genannten Klassen Eigenschaften hinzugefĂŒgt und sehen uns nun eine weitere Technik an, die nĂŒtzlich sein kann, wenn Konflikte zwischen Klassennamen oder Funktionen auftreten, z. B. wenn ein Funktionsname mit einem Python-SchlĂŒsselwort ĂŒbereinstimmt. Dazu können Sie Klassen, Funktionen, Ausnahmen und andere EntitĂ€ten umbenennen.

Zum Umbenennen einer EntitĂ€t wird die Annotation PyName verwendet , deren Wert einem neuen EntitĂ€tsnamen zugewiesen werden muss. Die Arbeit mit der PyName- Annotation wird im Beispiel aus dem Ordner pyfoo_cpp_05 gezeigt . Dieses Beispiel basiert auf dem vorherigen Beispiel.pyfoo_cpp_04 und unterscheidet sich davon durch die Datei pyfoocpp.sip , deren Inhalt nun folgendermaßen aussieht:

%Module(name=foocpp, language="C++")
%DefaultEncoding "UTF-8"
%DefaultDocstringFormat "deindented"
%DefaultDocstringSignature "prepended"

class Foo /PyName=Bar/ {
    %Docstring
    Class example from C++ library
    %End

    %TypeHeaderCode
    #include <foo.h>
    %End

    public:
        Foo(int, const char*);

        void set_int_val(int) /PyName=set_integer_value/;
        %Docstring
            Set integer value
        %End

        int get_int_val() /PyName=get_integer_value/;
        %Docstring
            Return integer value
        %End

        %Property(name=int_val, get=get_integer_value, set=set_integer_value)
        {
            %Docstring
                The property for integer value
            %End
        };

        void set_string_val(const char*) /PyName=set_string_value/;
        %Docstring
            Set string value
        %End

        char* get_string_val() /PyName=get_string_value/;
        %Docstring
            Return string value
        %End

        %Property(name=string_val, get=get_string_value, set=set_string_value)
        {
            %Docstring
                The property for string value
            %End
        };
};

In diesem Beispiel haben wir die Foo- Klasse in Bar- Klasse umbenannt und allen Methoden mithilfe der PyName- Annotation andere Namen zugewiesen . Ich denke, dass hier alles ganz einfach und klar ist. Das einzige, worauf man achten sollte, ist die Schaffung von Immobilien. In der % Property- Direktive mĂŒssen die Parameter get und set die Namen der Methoden angeben, wie sie in der Python-Klasse aufgerufen werden, und nicht die Namen, nach denen sie ursprĂŒnglich im C ++ - Code aufgerufen wurden.

Kompilieren Sie das Beispiel, installieren Sie es und sehen Sie, wie diese Klasse in Python aussehen wird:

>>> from foocpp import Bar
>>> help(Bar)

Help on class Bar in module foocpp:

class Bar(sip.wrapper)
 |  Class example from C++ library
 |  
 |  Method resolution order:
 |      Bar
 |      sip.wrapper
 |      sip.simplewrapper
 |      builtins.object
 |  
 |  Methods defined here:
 |  
 |  get_integer_value(...)
 |      get_integer_value(self) -> int
 |      Return integer value
 |  
 |  get_string_value(...)
 |      get_string_value(self) -> str
 |      Return string value
 |  
 |  set_integer_value(...)
 |      set_integer_value(self, int)
 |      Set integer value
 |  
 |  set_string_value(...)
 |      set_string_value(self, str)
 |      Set string value
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)
 |  
 |  int_val
 |      The property for integer value
 |  
 |  string_val
 |      The property for string value
 |  
 |  ----------------------------------------------------------------------
...

Es funktionierte! Wir haben es geschafft, die Klasse selbst und ihre Methoden umzubenennen.

Manchmal verwenden Bibliotheken die Vereinbarung, dass die Namen aller Klassen mit einem PrĂ€fix beginnen, z. B. mit dem Buchstaben „Q“ in Qt oder „wx“ in wxWidgets. Wenn Sie in Ihrer Python-Bindung alle Klassen umbenennen und solche PrĂ€fixe entfernen möchten , können Sie die Anweisung % AutoPyName verwenden , um die PyName- Annotation nicht fĂŒr jede Klasse festzulegen . Wir werden diese Direktive in diesem Artikel nicht berĂŒcksichtigen. Wir werden nur sagen, dass sich die Direktive % AutoPyName innerhalb der Direktive % Module befinden sollte, und uns auf ein Beispiel aus der Dokumentation beschrĂ€nken:

%Module PyQt5.QtCore
{
    %AutoPyName(remove_leading="Q")
}

Typkonvertierung hinzufĂŒgen


Beispiel mit der Klasse std :: wstring


Bisher haben wir uns Funktionen und Klassen angesehen, die mit einfachen Typen wie int und char * gearbeitet haben . FĂŒr diese Typen hat SIP automatisch einen Konverter zu und von Python-Klassen erstellt. Im folgenden Beispiel, das sich im Ordner pyfoo_cpp_06 befindet , wird der Fall betrachtet, in dem Klassenmethoden komplexere Objekte akzeptieren und zurĂŒckgeben, z. B. Zeichenfolgen aus STL. Um das Beispiel zu vereinfachen und die Konvertierung von Bytes in Unicode und umgekehrt nicht zu erschweren, wird in diesem Beispiel die Zeichenfolgenklasse std :: wstring verwendet . In diesem Beispiel soll gezeigt werden, wie Sie die Regeln fĂŒr die Konvertierung von C ++ - Klassen in und aus Python-Klassen manuell festlegen können.

In diesem Beispiel Ă€ndern wir die Foo- Klasse aus der Foo- Bibliothek. Jetzt sieht die Klassendefinition folgendermaßen aus (Datei foo.h ):

#ifndef FOO_LIB
#define FOO_LIB

#include <string>

using std::wstring;

class Foo {
    private:
        int _int_val;
        wstring _string_val;
    public:
        Foo(int int_val, wstring string_val);

        void set_int_val(int val);
        int get_int_val();

        void set_string_val(wstring val);
        wstring get_string_val();
};

#endif

Die Implementierung der Foo- Klasse in der Datei foo.cpp :

#include <string>

#include "foo.h"

using std::wstring;

Foo::Foo(int int_val, wstring string_val):
    _int_val(int_val), _string_val(string_val) {}

void Foo::set_int_val(int val) {
    _int_val = val;
}

int Foo::get_int_val() {
    return _int_val;
}

void Foo::set_string_val(wstring val) {
    _string_val = val;
}

wstring Foo::get_string_val() {
    return _string_val;
}

Und die Datei main.cpp zum ÜberprĂŒfen der FunktionalitĂ€t der Bibliothek:

#include <iostream>

#include "foo.h"

using std::cout;
using std::endl;

int main(int argc, char* argv[]) {
    auto foo = Foo(10, L"Hello");
    cout << L"int_val: " << foo.get_int_val() << endl;
    cout << L"string_val: " << foo.get_string_val().c_str() << endl;

    foo.set_int_val(0);
    foo.set_string_val(L"Hello world!");

    cout << L"int_val: " << foo.get_int_val() << endl;
    cout << L"string_val: " << foo.get_string_val().c_str() << endl;
}

Dateien foo.h , foo.cpp und main.cpp nach wie vor befinden sich in den foo Ordner . Der Erstellungsprozess fĂŒr Makefile und Bibliothek hat sich nicht geĂ€ndert. Es gibt auch keine wesentlichen Änderungen an den Dateien pyproject.toml und project.py .

Die Datei pyfoocpp.sip ist jedoch merklich komplizierter geworden:

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

class Foo {
    %TypeHeaderCode
    #include <foo.h>
    %End

    public:
        Foo(int, std::wstring);

        void set_int_val(int);
        int get_int_val();
        %Property(name=int_val, get=get_int_val, set=set_int_val)

        void set_string_val(std::wstring);
        std::wstring get_string_val();
        %Property(name=string_val, get=get_string_val, set=set_string_val)
};

%MappedType std::wstring
{
%TypeHeaderCode
#include <string>
%End

%ConvertFromTypeCode
    // Convert an std::wstring to a Python (Unicode) string
    PyObject* newstring;
    newstring = PyUnicode_FromWideChar(sipCpp->data(), -1);
    return newstring;
%End

%ConvertToTypeCode
    // Convert a Python (Unicode) string to an std::wstring
    if (sipIsErr == NULL) {
        return PyUnicode_Check(sipPy);
    }
    if (PyUnicode_Check(sipPy)) {
        *sipCppPtr = new std::wstring(PyUnicode_AS_UNICODE(sipPy));
        return 1;
    }
    return 0;
%End
};

Zur Veranschaulichung werden in der Datei pyfoocpp.sip keine Dokumentationszeilen hinzugefĂŒgt. Wenn wir nur die Deklaration der Foo- Klasse in der Datei pyfoocpp.sip ohne die nachfolgende Direktive % MappedType belassen wĂŒrden, wĂŒrden wir wĂ€hrend des Erstellungsprozesses den folgenden Fehler erhalten:

$ sip-wheel

These bindings will be built: pyfoocpp.
Generating the pyfoocpp bindings...
sip-wheel: std::wstring is undefined

Wir mĂŒssen explizit beschreiben, wie ein Objekt vom Typ std :: wstring in ein Python-Objekt konvertiert wird, und auch die inverse Transformation beschreiben. Um die Konvertierung zu beschreiben, mĂŒssen wir in der C-Sprache auf einem relativ niedrigen Niveau arbeiten und die Python / C-API verwenden . Da die Python / C-API ein großes Thema ist, das nur einen separaten Artikel, aber ein Buch verdient, werden in diesem Abschnitt nur die Funktionen betrachtet, die im Beispiel verwendet werden, ohne zu sehr ins Detail zu gehen.

Um Konvertierungen von C ++ - Objekten nach Python zu deklarieren und umgekehrt, ist die Direktive % MappedType vorgesehen , in der sich drei weitere Direktiven befinden können: % TypeHeaderCode , % ConvertToTypeCode und % ConvertFromTypeCode. Nach dem % MappedType- Ausdruck mĂŒssen Sie den Typ angeben, fĂŒr den Konverter erstellt werden. In unserem Fall beginnt die Direktive mit dem Ausdruck % MappedType std :: wstring . Wir haben die

Direktive % TypeHeaderCode bereits im Abschnitt Erstellen einer Bindung fĂŒr eine Bibliothek in C ++ erfĂŒllt . Ich möchte Sie daran erinnern, dass diese Direktive die verwendeten Typen deklarieren oder die Header-Dateien enthalten soll, in denen sie deklariert sind. In diesem Beispiel wird die Header - Datei String , wo die Klasse std :: string wird deklariert, wird verbunden innerhalb der % TypeHeaderCode Richtlinie . Jetzt mĂŒssen wir die Transformationen beschreiben



% ConvertFromTypeCode. Konvertieren von C ++ - Objekten in Python


Wir beginnen mit der Konvertierung von std :: wstring- Objekten in die Python- Klasse str . Diese Konvertierung im Beispiel lautet wie folgt:

%ConvertFromTypeCode
    // Convert an std::wstring to a Python (Unicode) string
    PyObject* newstring;
    newstring = PyUnicode_FromWideChar(sipCpp->data(), -1);
    return newstring;
%End

In dieser Direktive befindet sich die Variable sipCpp - ein Zeiger auf ein Objekt aus C ++ - Code, mit dem wir ein Python-Objekt erstellen und das erstellte Objekt mithilfe der return-Anweisung aus der Direktive zurĂŒckgeben mĂŒssen . In diesem Fall ist die Variable sipCpp vom Typ std :: wstring * . Verwenden Sie zum Erstellen der str- Klasse die Funktion PyUnicode_FromWideChar aus der Python / C-API. Diese Funktion akzeptiert ein Array (Zeiger) vom Typ const wchar_t * w als ersten Parameter und die GrĂ¶ĂŸe dieses Arrays als zweiten Parameter. Wenn Sie den Wert -1 als zweiten Parameter ĂŒbergeben, berechnet die Funktion PyUnicode_FromWideChar selbst die LĂ€nge mithilfe der Funktionwcslen .

Um die bekommen wchar_t * Array, verwendet , um die Daten - Methode aus dem std :: wstring Klasse .

Die Funktion PyUnicode_FromWideChar gibt im Fehlerfall einen Zeiger auf PyObject oder NULL zurĂŒck. PyObject ist ein beliebiges Python-Objekt. In diesem Fall handelt es sich um die Klasse str . In der Python / C-API erfolgt die Arbeit mit Objekten normalerweise ĂŒber PyObject * -Zeiger. In diesem Fall geben wir den PyObject * -Zeiger aus der % ConvertFromTypeCode- Direktive zurĂŒck .

% ConvertToTypeCode. Konvertieren Sie Python-Objekte in C ++


Die inverse Konvertierung von einem Python-Objekt (im Wesentlichen von PyObject * ) in die Klasse std :: wstring wird in der Direktive % ConvertToTypeCode beschrieben . Im Beispiel pyfoo_cpp_06 lautet die Konvertierung wie folgt:

%ConvertToTypeCode
    // Convert a Python (Unicode) string to an std::wstring
    if (sipIsErr == NULL) {
        return PyUnicode_Check(sipPy);
    }
    if (PyUnicode_Check(sipPy)) {
        *sipCppPtr = new std::wstring(PyUnicode_AS_UNICODE(sipPy));
        return 1;
    }
    return 0;
%End

Der Code der Direktive % ConvertToTypeCode sieht komplizierter aus, da er wĂ€hrend des Konvertierungsprozesses fĂŒr verschiedene Zwecke mehrmals aufgerufen wird. Innerhalb der % ConvertToTypeCode- Direktive erstellt SIP mehrere Variablen, die wir verwenden können (oder sollten).

Eine dieser Variablen, PyObject * sipPy, ist ein Python-Objekt, das Sie in diesem Fall benötigen, um eine Instanz der Klasse std :: wstring zu erstellen . Das Ergebnis muss in eine andere Variable geschrieben werden - sipCppPtr ist ein Doppelzeiger auf das erstellte Objekt, d. H. In unserem Fall ist diese Variable vom Typ std :: wstring ** .

Ein weiterer % ConvertToTypeCode, der innerhalb der Direktive erstellt wurdeDie Variable ist int * sipIsErr . Wenn der Wert dieser Variablen NULL ist , wird die Direktive % ConvertToTypeCode nur aufgerufen, um zu ĂŒberprĂŒfen, ob eine Typkonvertierung möglich ist. In diesem Fall sind wir nicht verpflichtet, die Transformation durchzufĂŒhren, sondern mĂŒssen nur prĂŒfen, ob dies grundsĂ€tzlich möglich ist. Wenn möglich, muss die Direktive einen Wert ungleich Null zurĂŒckgeben. Wenn keine Konvertierung möglich ist, mĂŒssen sie 0 zurĂŒckgeben. Wenn dieser Zeiger nicht NULL ist , mĂŒssen Sie die Konvertierung durchfĂŒhren. Wenn wĂ€hrend der Konvertierung ein Fehler auftritt, kann der ganzzahlige Fehlercode gespeichert werden in diese Variable (vorausgesetzt, diese Variable ist ein Zeiger auf int * ).

In diesem Beispiel wird das PyUnicode_Check- Makro verwendet , um zu ĂŒberprĂŒfen, ob sipPy eine Unicode-Zeichenfolge (Klasse str ) ist. Dabei wird ein Argument vom Typ PyObject * verwendet, wenn das ĂŒbergebene Argument eine Unicode-Zeichenfolge oder eine davon abgeleitete Klasse ist.

Die Konvertierung in ein C ++ - Objekt erfolgt mit der Zeichenfolge * sipCppPtr = new std :: wstring (PyUnicode_AS_UNICODE (sipPy)); . Hier wird das Makro PyUnicode_AS_UNICODE von der Python / C-API aufgerufen , die ein Array vom Typ Py_UNICODE * zurĂŒckgibt , das wchar_t * entspricht . Dieses Array wird an den Konstruktor der Klasse std :: wstring ĂŒbergeben. Wie oben erwĂ€hnt, wird das Ergebnis in der Variablen sipCppPtr gespeichert .

Im Moment ist die Anweisung PyUnicode_AS_UNICODE veraltet und es wird empfohlen, andere Makros zu verwenden. Dieses Makro wird jedoch verwendet, um das Beispiel zu vereinfachen.

Wenn die Konvertierung erfolgreich war, sollte die Direktive % ConvertToTypeCode einen Wert ungleich Null (in diesem Fall 1) und im Fehlerfall 0 zurĂŒckgeben.

PrĂŒfen


Wir haben die Konvertierung des Typs std :: wstring in str beschrieben und umgekehrt. Jetzt können wir sicherstellen, dass das Paket erfolgreich erstellt wurde und die Bindung ordnungsgemĂ€ĂŸ funktioniert. Rufen Sie zum Erstellen sip-whe auf , installieren Sie das Paket mit pip und ĂŒberprĂŒfen Sie die FunktionsfĂ€higkeit im Python-Befehlsmodus:

>>> from foocpp import Foo
>>> x = Foo(10, 'Hello')

>>> x.string_val
'Hello'

>>> x.string_val = ''
>>> x.string_val
''

>>> x.get_string_val()
''

Wie Sie sehen können, funktioniert alles, es gibt auch keine Probleme mit der russischen Sprache, d. H. Unicode-String-Konvertierungen wurden korrekt ausgefĂŒhrt.

Fazit


In diesem Artikel haben wir die Grundlagen der Verwendung von SIP zum Erstellen von Python-Bindungen fĂŒr in C und C ++ geschriebene Bibliotheken behandelt. Zuerst (im ersten Teil ) haben wir eine einfache Bibliothek in C erstellt und die Dateien herausgefunden, die fĂŒr die Arbeit mit SIP erstellt werden mĂŒssen. Die Datei pyproject.toml enthĂ€lt Informationen zum Paket (Name, Versionsnummer, Lizenz und Pfade zu Header- und Objektdateien). Mit der Datei project.py können Sie den Prozess des Erstellens des Python-Pakets beeinflussen, z. B. mit dem Erstellen der C / C ++ - Bibliothek beginnen oder dem Benutzer erlauben, den Speicherort der Header- und Objektdateien der Bibliothek anzugeben.

In der * .sip- Dateibeschreibt die Schnittstelle des Python-Moduls und listet die Funktionen und Klassen auf, die im Modul enthalten sein werden. Anweisungen und Anmerkungen werden verwendet , um die Schnittstelle in der * .sip- Datei zu beschreiben . Die Python-Klassenschnittstelle muss nicht mit der C ++ - Klassenschnittstelle ĂŒbereinstimmen. Sie können beispielsweise Klassen mithilfe der Direktive % Property Eigenschaften hinzufĂŒgen , EntitĂ€ten mithilfe der Anweisung / PyName / annotation umbenennen und Dokumentationszeilen mithilfe der Direktive % Docstring hinzufĂŒgen .

Elementartypen wie int , char , char *usw. SIP konvertiert automatisch in Ă€hnliche Python-Klassen. Wenn Sie jedoch eine komplexere Konvertierung durchfĂŒhren mĂŒssen, mĂŒssen Sie diese mithilfe der Python / C-API selbst in der % MappedType- Direktive programmieren . Die Konvertierung von der Python-Klasse in C ++ muss in der verschachtelten Direktive % ConvertToTypeCode erfolgen . Das Konvertieren von einem C ++ - Typ in eine Python-Klasse muss in der verschachtelten Direktive % ConvertFromTypeCode erfolgen .

Einige Anweisungen wie % DefaultEncoding , % DefaultDocstringFormat und % DefaultDocstringSignature sind Hilfsanweisungen und ermöglichen das Festlegen von Standardwerten fĂŒr FĂ€lle, in denen einige Anmerkungsparameter nicht explizit festgelegt werden.

In diesem Artikel haben wir nur die grundlegenden und einfachsten Anweisungen und Anmerkungen untersucht, aber viele davon wurden ignoriert. Beispielsweise gibt es Anweisungen zum Verwalten von GIL, zum Erstellen neuer Python-Ausnahmen, zum manuellen Verwalten von Speicher- und Garbage Collectors, zum Optimieren von Klassen fĂŒr verschiedene Betriebssysteme und viele andere, die beim Erstellen von Bindungen komplexer C / C ++ - Bibliotheken hilfreich sein können. Wir haben auch das Problem der Erstellung von Paketen fĂŒr verschiedene Betriebssysteme umgangen und uns darauf beschrĂ€nkt, unter Linux mit gcc / g ++ - Compilern zu erstellen.

Verweise



All Articles