إنشاء روابط Python لمكتبات C / C ++ باستخدام SIP. الجزء 2

في الجزء الأول من المقالة ، درسنا أساسيات العمل مع الأداة المساعدة SIP المصممة لإنشاء روابط Python للمكتبات المكتوبة بلغة C و C ++. نظرنا في الملفات الأساسية التي تحتاج إلى إنشائها للعمل مع SIP وبدأنا في النظر في التوجيهات والتعليقات التوضيحية. حتى الآن ، قمنا بعمل الربط لمكتبة بسيطة مكتوبة في C. في هذا الجزء ، سنكتشف كيفية إجراء الربط لمكتبة C ++ التي تحتوي على فئات. باستخدام مثال هذه المكتبة ، سنرى ما التقنيات التي يمكن أن تكون مفيدة عند العمل مع مكتبة موجهة للكائنات ، وفي نفس الوقت سنتعامل مع توجيهات وشروح جديدة لنا.

جميع الأمثلة على المقال متوفرة في مستودع جيثب على: https://github.com/Jenyay/sip-examples .

جعل ملزمة لمكتبة في C ++


المثال التالي ، الذي سننظر فيه ، موجود في مجلد pyfoo_cpp_01 .

أولاً ، قم بإنشاء مكتبة سنقوم بربطها. ستظل المكتبة موجودة في مجلد foo وستحتوي على فصل واحد - Foo . ملف رأس foo.h مع تعريف هذه الفئة هو كما يلي:

#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

هذه فئة بسيطة مع اثنين من الحروف والأساسات التي تحدد وتعيد القيم من النوع int و char * . تنفيذ الفصل كما يلي:

#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;
}

لاختبار وظائف المكتبة ، يحتوي مجلد foo أيضًا على ملف main.cpp باستخدام فئة 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;
}

لإنشاء مكتبة foo ، استخدم ملف 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)/*

الفرق من Makefile في الأمثلة السابقة ، بالإضافة إلى تغيير المترجم من gcc إلى g ++ ، هو أنه تم إضافة خيار آخر -fPIC للتجميع ، والذي يخبر المترجم بوضع الشفرة في المكتبة بطريقة معينة (ما يسمى "الرمز المستقل عن الموضع"). نظرًا لأن هذه المقالة ليست حول المترجمين ، فلن نفحص بمزيد من التفصيل ما تفعله هذه المعلمة ولماذا هناك حاجة إليها.

لنبدأ في ربط هذه المكتبة. ملفات pyproject.toml و project.py لم تتغير تقريبًا عن الأمثلة السابقة. إليك ما يبدو عليه ملف pyproject.toml الآن :

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

الآن سيتم تغليف أمثلةنا المكتوبة بلغة C ++ في حزمة pyfoocpp Python ، ربما يكون هذا هو التغيير الوحيد الملحوظ في هذا الملف. يظل

ملف project.py كما هو في المثال 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()

وهنا سننظر في ملف pyfoocpp.sip بمزيد من التفصيل. دعني أذكرك بأن هذا الملف يصف واجهة وحدة Python المستقبلية: ما يجب أن يتضمنه ، وما يجب أن تبدو عليه واجهة الفصل ، وما إلى ذلك. ملف .sip غير مطلوب لتكرار ملف رأس المكتبة ، على الرغم من أنه سيكون لديهم الكثير من القواسم المشتركة. داخل هذه الفئة ، يمكن إضافة طرق جديدة لم تكن في الفصل الأصلي. أولئك. يمكن للواجهة الموضحة في ملف .sip تخصيص فئات المكتبة وفقًا للمبادئ المقبولة في Python ، إذا لزم الأمر. في ملف pyfoocpp.sip سنرى توجيهات جديدة لنا.

أولاً ، دعنا نرى ما يحتويه هذا الملف:

%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();
};

يجب أن تكون السطور الأولى واضحة لنا بالفعل من الأمثلة السابقة. في توجيه ٪ Module ، نشير إلى اسم وحدة Python التي سيتم إنشاؤها (أي لاستخدام هذه الوحدة ، سنحتاج إلى استخدام استيراد foocpp أو من أوامر استيراد foocpp ... في نفس التوجيه ، نشير إلى أن لدينا الآن اللغة - C ++. يقوم التوجيه٪ DefaultEncoding بتعيين الترميز الذي سيتم استخدامه لتحويل سلسلة Python إلى أنواع char و const char و char * و const char * .

ويلي ذلك إعلان الواجهة لفئة Foo . مباشرة بعد إعلان فئة Fooلا يزال هناك عدم استخدام توجيه ٪ TypeHeaderCode ، الذي ينتهي بالتوجيه ٪ End . و TypeHeaderCode التوجيه٪ يجب أن يحتوي على التعليمات البرمجية التي تعلن واجهة من فئة C ++ التي يتم الآن إنشاء المجمع. كقاعدة ، يكفي في هذا التوجيه تضمين ملف الرأس مع تعريف الفئة.

بعد ذلك ، يتم سرد طرق الفصل التي سيتم تحويلها إلى أساليب فئة Foo للغة Python. من المهم ملاحظة أننا في هذه المرحلة نعلن فقط عن الطرق العامة التي يمكن الوصول إليها من فئة Foo في Python (نظرًا لعدم وجود أعضاء خاصين ومحميين في Python). نظرًا لأننا استخدمنا توجيه ٪ DefaultEncoding في البداية، ثم في الأساليب التي تتخذ حجج من نوع CONST شار * ، لا يمكنك استخدام ترميز الشرح لتحديد الترميز لتحويل هذه المعايير إلى سلاسل بيثون، والعكس بالعكس.

الآن نحن بحاجة فقط إلى تجميع حزمة pyfoocpp Python واختبارها. ولكن قبل تجميع حزمة عجلة كاملة ، دعنا نستخدم الأمر sip-build ونرى ما هي الملفات المصدر التي سينشئها SIP لتجميعها لاحقًا ، ونحاول أن نجد فيها شيئًا مشابهًا للفئة التي سيتم إنشاؤها في كود Python. للقيام بذلك ، يجب استدعاء أمر بناء sip أعلاه في مجلد pyfoo_cpp_01 . نتيجة لذلك ، سيتم إنشاء مجلد البناء . بالمحتويات التالية:

بناء
└── فووكب
    ├── 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


كمهمة إضافية ، فكر بعناية في ملف sipfoocppFoo.cpp (لن نناقشه بالتفصيل في هذه المقالة):

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

الآن قم ببناء الحزمة باستخدام الأمر sip-wheel . بعد تنفيذ هذا الأمر ، إذا سارت الأمور على ما يرام ، سيتم إنشاء ملف pyfoocpp-0.1-cp38-cp38-manylinux1_x86_64.whl أو باسم مشابه. قم بتثبيته باستخدام الأمر pip install --user pyfoocpp-0.1-cp38-cp38-manylinux1_x86_64.whl وقم بتشغيل مترجم Python للتحقق:

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

يعمل! وهكذا ، قمنا للتو بإنشاء وحدة Python مع ربط لفئة في C ++. علاوة على ذلك ، سنجلب الجمال لهذه الفئة ونضيف العديد من المرافق.

أضف خصائص


الفصول التي تم إنشاؤها باستخدام SIP ليست مطلوبة لتكرار واجهة فئة C ++ بالضبط. على سبيل المثال ، في صف Foo لدينا ، هناك نوعان من الحروف والأساسين ، يمكن دمجهما بوضوح في خاصية لجعل الفصل أكثر "Python". تُعد إضافة الخصائص باستخدام sip أمرًا سهلاً بما يكفي عند الانتهاء من ذلك ، يظهر مثال في مجلد pyfoo_cpp_02 .

هذا المثال مشابه للمثال السابق ، والفرق الرئيسي في ملف pyfoocpp.sip ، والذي يبدو الآن كما يلي:

%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)
};

كما ترى ، كل شيء بسيط للغاية. لإضافة خاصية ، الهدف من خاصية٪ property موجه ، والذي يحتوي على معلمتين مطلوبتين: اسم لتحديد اسم الخاصية ، والحصول على تحديد طريقة تقوم بإرجاع قيمة (getter). قد لا يكون هناك محدد ، ولكن إذا كانت الخاصية تحتاج أيضًا إلى تعيين قيم ، يتم تحديد طريقة الضبط كقيمة المعلمة المحددة . في مثالنا ، يتم إنشاء الخصائص بطريقة مباشرة إلى حد ما ، حيث توجد بالفعل وظائف تعمل كرسائل ومستوطنين.

يمكننا فقط جمع الحزمة باستخدام الأمر sip-wheel ، وتثبيتها ، وبعد ذلك سنتحقق من تشغيل الخصائص في وضع أوامر مترجم بايثون:

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

وكما ترون من المثال من استخدام فو الطبقة ، و int_val و خصائص string_val العمل على حد سواء للقراءة والكتابة.

أضف سطور التوثيق


سنستمر في تحسين صفنا في Foo . يوضح المثال التالي ، الموجود في المجلد pyfoo_cpp_03 ، كيفية إضافة سطور التوثيق (docstring) إلى عناصر متنوعة من الفصل الدراسي. يعتمد هذا المثال على المثال السابق ، والتغيير الرئيسي فيه يتعلق بملف pyfoocpp.sip . هنا محتوياته:

%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
        };
};

كما فهمت بالفعل ، من أجل إضافة سطور التوثيق إلى أي عنصر من عناصر الفصل ، تحتاج إلى استخدام التوجيه ٪ Docstring . يوضح هذا المثال عدة طرق لاستخدام هذا التوجيه. لفهم هذا المثال بشكل أفضل ، دعنا نجمع على الفور حزمة pyfoocpp باستخدام الأمر sip-wheel ، قم بتثبيته ، وسوف نكتشف بشكل متسلسل أي معلمة من هذا التوجيه تؤثر على ما ، بالنظر إلى خطوط التوثيق الناتجة في وضع أمر Python. دعني أذكرك بأنه يتم تخزين خطوط التوثيق كأعضاء لكائنات __ doc__ التي تنتمي إليها هذه الخطوط.

السطر الأول من الوثائق لفئة Foo .. كما ترى ، تقع جميع أسطر التوثيق بين توجيهات٪ Docstring و ٪ End . لا تستخدم الأسطر 5-7 من هذا المثال أي معلمات إضافية لتوجيه٪ Docstring ، لذلك سيتم كتابة سطر التوثيق إلى فئة Foo كما هي. هذا هو السبب في عدم وجود مسافة بادئة في السطور 5-7 ، وإلا ستقع المسافات البادئة أمام سطر التوثيق أيضًا في Foo .__ doc__. سوف نتأكد من أن فئة Foo تحتوي بالفعل على سطر التوثيق الذي قدمناه:

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

يستخدم التوجيه ٪ Docstring التالي ، الموجود في السطور 17-19 ، معلمتين في وقت واحد. يمكن أن تأخذ معلمة التنسيق واحدة من قيمتين: "أولية" أو "مزالة". في الحالة الأولى ، يتم حفظ أسطر التوثيق أثناء كتابتها ، وفي الحالة الثانية ، يتم حذف أحرف المسافات الأولية (وليس علامات الجدولة). يمكن تعيين القيمة الافتراضية للحالة إذا لم يتم تحديد معلمة التنسيق باستخدام التوجيه ٪ DefaultDocstringFormat ( سنأخذها في الاعتبار لاحقًا) ، وإذا لم يتم تحديدها ، فمن المفترض أن التنسيق = "raw" .

بالإضافة إلى أسطر التوثيق المحددة ، يضيف SIP وصفًا لتوقيعه (أنواع المتغيرات المتوقعة عند الإدخال ونوع الدالة التي ترجع) إلى أسطر توثيق الوظائف. تشير معلمة التوقيع إلى مكان وضع هذا التوقيع: قبل سطر التوثيق المحدد ( signature = "prepended" ) ، وبعده ( signature = "appended" ) أو عدم إضافة التوقيع ( signature = "تجاهل" ).

مثالنا يحدد توقيع = "إرفاق مسبقا" المعلمة ل get_int_val و ظائف set_int_val ، فضلا عن توقيع = "إلحاق" ل get_string_val و ظائف set_string_val. تمت إضافة المعلمة format = "deindented" أيضًا لإزالة المسافات في بداية سطر التوثيق. دعونا نتحقق من كيفية عمل هذه المعلمات في Python:

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

كما ترى ، باستخدام معلمة التوقيع الخاصة بتوجيه٪ Docstring ، يمكنك تغيير موضع وصف توقيع الوظيفة في سطر التوثيق.

الآن فكر في إضافة سطر وثائق إلى الخصائص. لاحظ أنه في هذه الحالة ، يتم تضمين تعليمات٪ Docstring ... ٪ End بين أقواس بعد توجيه خاصية٪. يتم وصف تنسيق التسجيل هذا في وثائق التوجيه ٪ Property .

لاحظ أيضًا كيف نحدد معلمة التوجيه ٪ Docstring . مثل هذا التنسيق لكتابة التوجيهات ممكن إذا قمنا بتعيين المعلمة الأولى فقط للتوجيه (في هذه الحالة ، معلمة التنسيق) وهكذا ، في هذا المثال ، يتم استخدام ثلاث طرق لاستخدام التوجيهات في وقت واحد.

تأكد من تعيين سطر التوثيق للخصائص:

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


دعونا تبسيط هذا المثال عن طريق تعيين القيم الافتراضية لل تنسيق و توقيع المعلمات باستخدام ٪ DefaultDocstringFormat و ٪ DefaultDocstringSignature التوجيهات . يظهر استخدام هذه التوجيهات في المثال من مجلد pyfoo_cpp_04 . يحتوي ملف pyfoocpp.sip في هذا المثال على التعليمات البرمجية التالية:

%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
        };
};

في بداية الملف ، تمت إضافة السطور ٪ DefaultDocstringFormat "deindented" و ٪ DefaultDocstringSignature "المضاف" ، ثم تمت إزالة كافة المعلمات من توجيه ٪ Docstring .

بعد تجميع هذا المثال وتثبيته ، يمكننا أن نرى كيف يبدو وصف فئة Foo الآن ، والذي يعرضه الأمر help (Foo) :

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

كل شيء يبدو أنيقًا تمامًا ومن نفس النوع.

إعادة تسمية الفئات والأساليب


كما قلنا بالفعل ، لا يجب أن تتطابق الواجهة التي توفرها روابط Python مع الواجهة التي توفرها مكتبة C / C ++. أضفنا خصائص إلى الفئات أعلاه ، وسنلقي نظرة الآن على تقنية أخرى يمكن أن تكون مفيدة إذا ظهرت تضاربات في أسماء الفئات أو الوظائف ، على سبيل المثال ، إذا كان اسم الوظيفة يطابق بعض الكلمات الرئيسية في Python. للقيام بذلك ، يمكنك إعادة تسمية الفئات والوظائف والاستثناءات والكيانات الأخرى.

لإعادة تسمية كيان ، يتم استخدام التعليق التوضيحي PyName ، ويجب تعيين قيمة كيان جديد له. يظهر العمل مع التعليق التوضيحي PyName في المثال من المجلد pyfoo_cpp_05 . هذا المثال مبني على المثال السابق.pyfoo_cpp_04 ويختلف عنه بواسطة ملف pyfoocpp.sip ، الذي تبدو محتوياته الآن كما يلي:

%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
        };
};

في هذا المثال ، قمنا بإعادة تسمية فئة Foo إلى فئة Bar ، وقمنا أيضًا بتعيين أسماء أخرى لجميع الطرق باستخدام التعليق التوضيحي PyName . أعتقد أن كل شيء هنا بسيط وواضح ، الشيء الوحيد الذي يستحق الانتباه إليه هو إنشاء العقارات. في التوجيه ٪ Property ، يجب أن تحدد معلمات get and set أسماء الطرق ، حيث سيتم استدعاؤهما في فئة Python ، وليس الأسماء التي تم استدعاؤهما في الأصل إلى كود C ++.

قم بتجميع المثال وتثبيته وانظر كيف سيبدو هذا الفصل في Python:

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

انها عملت! تمكنا من إعادة تسمية الصف نفسه وأساليبه.

تستخدم المكتبات أحيانًا الاتفاق على أن أسماء جميع الفئات تبدأ ببادئة ، على سبيل المثال ، بالحرف "Q" في Qt أو "wx" في wxWidgets. إذا كنت ترغب في إعادة تسمية جميع الفئات في ربط Python ، والتخلص من هذه البادئات ، ثم حتى لا تضبط تعليق PyName لكل فئة ، يمكنك استخدام التوجيه ٪ AutoPyName . لن نأخذ هذا الأمر بعين الاعتبار في هذه المقالة ، سنقول فقط أن توجيه ٪ AutoPyName يجب أن يكون موجودًا داخل توجيه ٪ Module ونقتصر على مثال من الوثائق:

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

أضف نوع التحويل


مثال باستخدام فئة std :: wstring


لقد نظرنا حتى الآن في الوظائف والفصول التي عملت مع أنواع بسيطة مثل int و char * . لهذه الأنواع ، قام SIP تلقائيًا بإنشاء محول من فئات Python وإليها. في المثال التالي ، الموجود في مجلد pyfoo_cpp_06 ، سنأخذ في الاعتبار الحالة عندما تقبل طرق الفصل وترجع كائنات أكثر تعقيدًا ، على سبيل المثال ، سلاسل من STL. لتبسيط المثال وعدم تعقيد تحويل وحدات البايت إلى Unicode والعكس ، سيتم استخدام فئة السلسلة std :: wstring في هذا المثال . تتمثل فكرة هذا المثال في إظهار كيف يمكنك تعيين القواعد يدويًا لتحويل فئات C ++ من فئات Python وإليها.

في هذا المثال ، سنقوم بتغيير فئة Foo من مكتبة foo. الآن سيبدو تعريف الفصل بهذا الشكل (file 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

تطبيق فئة Foo في ملف 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;
}

وملف main.cpp للتحقق من وظائف المكتبة:

#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;
}

الملفات foo.h و foo.cpp و main.cpp ، كما كان من قبل ، موجودة في مجلد foo . ماكيفيلي لم يتغير عملية الإنشاء والمكتبات. لا توجد أيضًا تغييرات كبيرة في ملفات pyproject.toml و project.py .

لكن ملف pyfoocpp.sip أصبح أكثر تعقيدًا بشكل ملحوظ:

%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
};

لأغراض التوضيح ، لا يضيف ملف pyfoocpp.sip سطور التوثيق. إذا تركنا إعلان فئة Foo فقط في ملف pyfoocpp.sip بدون توجيه ٪ MappedType اللاحق ، فسوف نحصل على الخطأ التالي أثناء عملية البناء:

$ sip-wheel

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

نحن بحاجة إلى وصف صريح لكيفية تحويل كائن من نوع std :: wstring إلى بعض عناصر Python ، وكذلك وصف التحويل العكسي. لوصف التحويل ، سنحتاج إلى العمل على مستوى منخفض إلى حد ما في لغة C واستخدام Python / C API . نظرًا لأن Python / C API هو موضوع كبير يستحق حتى مقالة منفصلة ، ولكن كتابًا ، في هذا القسم ، سننظر فقط في تلك الوظائف المستخدمة في المثال ، دون الخوض في التفاصيل.

لإعلان التحويلات من كائنات C ++ إلى Python والعكس صحيح ، فإن توجيه ٪ MappedType مخصص ، حيث قد يكون هناك ثلاثة توجيهات أخرى: ٪ TypeHeaderCode و ٪ ConvertToTypeCode و ٪ ConvertFromTypeCode. بعد تعبير ٪ MappedType ، تحتاج إلى تحديد النوع الذي سيتم إنشاء المحولات له. في حالتنا ، يبدأ التوجيه بالتعبير ٪ MappedType std :: wstring . لقد اجتمعنا بالفعل مع

التوجيه ٪ TypeHeaderCode في القسم إنشاء ربط لمكتبة في C ++ . دعني أذكرك بأن الهدف من هذا التوجيه هو إعلان الأنواع المستخدمة أو تضمين ملفات العناوين التي تم التصريح عنها. في هذا المثال ، يتم توصيل سلسلة ملف الرأس ، حيث تم التصريح عن الفئة std :: string ، داخل التوجيه ٪ TypeHeaderCode . الآن نحن بحاجة إلى وصف التحولات



٪ ConvertFromTypeCode. تحويل كائنات C ++ إلى Python


نبدأ بتحويل الأمراض المنقولة جنسيا :: wstring الكائنات إلى بيثون الدرجة شارع . هذا التحويل في المثال كما يلي:

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

داخل هذا التوجيه ، لدينا متغير sipCpp - مؤشر لكائن من كود C ++ ، والذي نحتاج من خلاله إلى إنشاء كائن Python وإرجاع الكائن الذي تم إنشاؤه من التوجيه باستخدام عبارة الإرجاع . في هذه الحالة ، يكون المتغير sipCpp من النوع std :: wstring * . لإنشاء فئة str ، استخدم الدالة PyUnicode_FromWideChar من Python / C API. تقبل هذه الدالة مصفوفة (مؤشر) من النوع const wchar_t * w كمعلمة أولى ، وحجم هذا الصفيف كمعلمة ثانية. إذا قمت بتمرير القيمة -1 كمعلمة ثانية ، فإن الدالة PyUnicode_FromWideChar نفسها ستحسب الطول باستخدام الوظيفةwcslen .

للحصول على الصفيف wchar_t * ، استخدم طريقة البيانات من فئة std :: wstring . ترجع

الدالة PyUnicode_FromWideChar مؤشر إلى PyObject أو NULL في حالة حدوث خطأ. PyObject هو أي كائن Python ، في هذه الحالة سيكون فئة str . في Python / C API ، يحدث العمل مع الكائنات عادةً من خلال مؤشرات PyObject * ، لذلك في هذه الحالة ، نعيد مؤشر PyObject * من التوجيه ٪ ConvertFromTypeCode .

٪ ConvertToTypeCode. تحويل كائنات Python إلى C ++


يتم وصف التحويل العكسي من كائن Python (بشكل أساسي من PyObject * ) إلى فئة std :: wstring في التوجيه ٪ ConvertToTypeCode . في مثال pyfoo_cpp_06 ، يكون التحويل كما يلي:

%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

يبدو رمز الأمر ٪ ConvertToTypeCode أكثر تعقيدًا ، لأنه أثناء عملية التحويل يتم استدعاؤه عدة مرات لأغراض مختلفة. داخل توجيه ٪ ConvertToTypeCode ، يقوم SIP بإنشاء العديد من المتغيرات التي يمكننا (أو ينبغي) استخدامها.

أحد هذه المتغيرات ، PyObject * sipPy ، هو كائن Python تحتاج إلى إنشاء مثيل لفئة std :: wstring في هذه الحالة . يجب كتابة النتيجة إلى متغير آخر - sipCppPtr هو مؤشر مزدوج للكائن الذي تم إنشاؤه ، أي في حالتنا ، سيكون هذا المتغير من النوع std :: wstring ** .

تم إنشاء ٪ ConvertToTypeCode آخر داخل التوجيهالمتغير هو int * sipIsErr . إذا كانت قيمة هذا المتغير فارغة ، فيتم استدعاء التوجيه ٪ ConvertToTypeCode فقط للتحقق مما إذا كان تحويل النوع ممكنًا. في هذه الحالة ، لسنا ملزمين بتنفيذ التحول ، ولكننا بحاجة فقط للتحقق مما إذا كان ذلك ممكنًا من حيث المبدأ. إذا كان ذلك ممكنًا ، فيجب أن يُرجع التوجيه قيمة غير صفرية ، وإلا ، إذا لم يكن التحويل ممكنًا ، فيجب عليهم إرجاع 0. إذا لم يكن هذا المؤشر فارغًا ، فأنت بحاجة إلى إجراء التحويل ، وإذا حدث خطأ أثناء التحويل ، يمكن حفظ رمز الخطأ الصحيح في هذا المتغير (بالنظر إلى أن هذا المتغير هو مؤشر إلى int * ).

في هذا المثال ، للتحقق من أن sipPy عبارة عن سلسلة unicode (فئة str ) ، يتم استخدام الماكرو PyUnicode_Check ، والذي يأخذ وسيطة من النوع PyObject * إذا كانت الوسيطة التي تم تمريرها عبارة عن سلسلة unicode أو فئة مشتقة منها.

يتم التحويل إلى كائن C ++ باستخدام السلسلة * sipCppPtr = new std :: wstring (PyUnicode_AS_UNICODE (sipPy)) ؛ . يستدعي ذلك الماكرو PyUnicode_AS_UNICODE من Python / C API ، والذي يُرجع مصفوفة من النوع Py_UNICODE * ، وهو ما يعادل wchar_t * . يتم تمرير هذا الصفيف إلى مُنشئ فئة std :: wstring. كما ذكر أعلاه ، يتم تخزين النتيجة في متغير sipCppPtr .

في الوقت الحالي ، تم إيقاف توجيه PyUnicode_AS_UNICODE ويوصى باستخدام وحدات ماكرو أخرى ، ولكن يتم استخدام هذا الماكرو لتبسيط المثال.

إذا كان التحويل ناجحًا ، يجب أن يُرجع التوجيه ٪ ConvertToTypeCode قيمة غير صفرية (في هذه الحالة 1) ، وفي حالة حدوث خطأ ، يجب أن تُرجع 0.

التحقق من


وصفنا تحويل النوع std :: wstring إلى str والعكس بالعكس ، الآن يمكننا التأكد من أن الحزمة قد تم بناؤها بنجاح وعمل الربط كما ينبغي. للبناء ، قم باستدعاء عجلة sip ، ثم قم بتثبيت الحزمة باستخدام النقطة وتحقق من قابلية التشغيل في وضع أمر Python:

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

>>> x.string_val
'Hello'

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

>>> x.get_string_val()
''

كما ترى ، كل شيء يعمل ، لا توجد مشاكل في اللغة الروسية أيضًا ، أي تم تنفيذ تحويلات سلسلة Unicode بشكل صحيح.

استنتاج


في هذه المقالة ، تناولنا أساسيات استخدام SIP لإنشاء روابط Python للمكتبات المكتوبة بلغة C و C ++. أولاً (في الجزء الأول ) أنشأنا مكتبة بسيطة في C واكتشفنا الملفات التي يجب إنشاؤها للعمل مع SIP. يحتوي ملف pyproject.toml على معلومات حول الحزمة (الاسم ورقم الإصدار والترخيص ومسارات ملفات الرأس والكائن). باستخدام ملف project.py ، يمكنك التأثير على عملية بناء حزمة Python ، على سبيل المثال ، البدء في بناء مكتبة C / C ++ أو السماح للمستخدم بتحديد موقع رأس الصفحة وملفات الكائنات في المكتبة.

في ملف * .sipيصف واجهة وحدة Python التي تسرد الوظائف والفئات التي سيتم تضمينها في الوحدة. تُستخدم التوجيهات والتعليقات التوضيحية لوصف الواجهة في ملف * .sip . ليس من الضروري أن تتطابق واجهة فئة Python مع واجهة فئة C ++. على سبيل المثال ، يمكنك إضافة خصائص إلى الفئات باستخدام التوجيه ٪ Property ، وإعادة تسمية الكيانات باستخدام / PyName / التعليق التوضيحي ، وإضافة سطور التوثيق باستخدام التوجيه ٪ Docstring .

الأنواع الأولية مثل int و char و char *إلخ يتحول SIP تلقائيًا إلى فئات Python مماثلة ، ولكن إذا كنت بحاجة إلى إجراء تحويل أكثر تعقيدًا ، فأنت بحاجة إلى برمجته بنفسك داخل توجيه ٪ MappedType باستخدام Python / C API. يجب أن يتم التحويل من فئة Python إلى C ++ في التوجيه المتداخل ٪ ConvertToTypeCode . يجب أن يتم التحويل من نوع C ++ إلى فئة Python في التوجيه المتداخل ٪ ConvertFromTypeCode .

بعض التوجيهات مثل ٪ DefaultEncoding و ٪ DefaultDocstringFormat و ٪ DefaultDocstringSignature مساعدة وتسمح لك بتعيين القيم الافتراضية للحالات عندما لا يتم تعيين بعض معلمات التعليقات التوضيحية بشكل صريح.

في هذه المقالة ، درسنا فقط التوجيهات والشروح الأساسية والأبسط ، ولكن تم تجاهل العديد منها. على سبيل المثال ، هناك توجيهات لإدارة GIL ، لإنشاء استثناءات Python جديدة ، لإدارة مجمعي الذاكرة والقمامة يدويًا ، لتعديل الفصول لأنظمة التشغيل المختلفة ، والعديد من الآخرين التي يمكن أن تكون مفيدة عند إنشاء روابط مكتبة C / C ++ معقدة. لقد تحايلنا أيضًا على مسألة إنشاء حزم لأنظمة تشغيل مختلفة ، وحصرنا أنفسنا في البناء تحت Linux باستخدام مترجمين gcc / g ++.

المراجع



All Articles