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

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

هناك العديد من الأدوات لإنشاء روابط Python ، بدءًا من المستوى الأدنى مثل Python / C API إلى المستوى الأعلى مثل SWIG و SIP .

لم يكن لدي هدف مقارنة الطرق المختلفة لإنشاء روابط Python ، ولكن أود أن أتحدث عن أساسيات استخدام أداة واحدة ، وهي SIP . في البداية ، تم تصميم SIP لإنشاء ربط حول مكتبة Qt - PyQt ، ويستخدم أيضًا لتطوير مكتبات Python كبيرة أخرى ، على سبيل المثال ، wxPython .

في هذه المقالة ، سيتم استخدام gcc كمترجم لـ C ، وسيتم استخدام g ++ كمترجم C ++. تم اختبار جميع الأمثلة تحت Arch Linux و Python 3.8. من أجل عدم تعقيد الأمثلة ، لم يتم تضمين موضوع الترجمة لأنظمة تشغيل مختلفة واستخدام مترجمين مختلفين (على سبيل المثال ، Visual Studio) في نطاق هذه المقالة.

يمكنك تنزيل جميع الأمثلة لهذه المقالة من المستودع على github .
يقع المستودع الذي يحتوي على مصادر SIP على https://www.riverbankcomputing.com/hg/sip/ . يستخدم Mercurial كنظام التحكم في الإصدار لـ SIP.

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


كتابة مكتبة في ج


يقع هذا المثال في مجلد pyfoo_c_01 في المصدر ، ولكن في هذه المقالة سنفترض أننا نقوم بكل شيء من البداية.

لنبدأ بمثال بسيط. أولاً ، سننشئ مكتبة C بسيطة ، والتي سنقوم بتشغيلها بعد ذلك من نص Python النصي. دع مكتبتنا تكون الوظيفة الوحيدة

int foo(char*);

الذي سيأخذ سلسلة ويعيد طوله مضروبا في 2. قد يبدو

ملف الرأس foo.h ، على سبيل المثال ، على النحو التالي :

#ifndef FOO_LIB
#define FOO_LIB

int foo(char* str);

#endif

والملف مع تنفيذ foo.cpp :

#include <string.h>

#include "foo.h"

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

لاختبار وظائف المكتبة ، سنكتب برنامج main.c بسيط :

#include <stdio.h>

#include "foo.h"

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

للدقة ، قم بإنشاء ملف 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)/*

دع جميع مصادر مكتبة foo موجودة في مجلد فرعي لـ foo في المجلد المصدر:

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


نذهب إلى مجلد foo ونجمع المصادر باستخدام الأمر

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

سيتم وضع نتيجة التجميع في مجلد الحاوية داخل مجلد foo :

foo_c_01 /
└── فو
    ├── بن
    │ ├── foo.o
    │ ├── libfoo.a
    ├── ├── الرئيسي
    │ └── main.o
    ├── foo.c
    ├── foo.h
    ├── main.c
    └── Makefile


قمنا بتجميع مكتبة للربط الثابت وبرنامج يستخدمه تحت اسم main . بعد التجميع ، يمكنك التحقق من أن البرنامج الرئيسي يبدأ.

دعونا نجعل من Python ملزمة على مكتبة foo.

أساسيات SIP


تحتاج أولاً إلى تثبيت SIP. يتم ذلك بشكل قياسي ، كما هو الحال مع جميع المكتبات الأخرى التي تستخدم pip:

pip install --user sip

بالطبع ، إذا كنت تعمل في بيئة افتراضية ، فيجب عدم تحديد المعلمة --user ، التي تشير إلى أن مكتبة SIP يجب تثبيتها في مجلد المستخدم ، وليس عالميًا في النظام ،.

ما الذي يتعين علينا القيام به حتى يمكن استدعاء مكتبة foo من كود Python؟ كحد أدنى ، تحتاج إلى إنشاء ملفين: أحدهما بتنسيق TOML وتسميته pyproject.toml ، والثاني هو ملف بامتداد .sip. دعونا نتعامل مع كل واحد منهم بالتتابع.

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

ستكون الملفات المطلوبة لـ SIP موجودة في نفس المجلد مثل مجلد foo .

pyproject.toml


ملف pyproject.toml ليس اختراعًا لمطوري SIP ، ولكن تنسيق وصف مشروع Python الموصوف في PEP 517 "تنسيق مستقل لنظام البناء لأشجار المصدر" وفي PEP 518 "تحديد الحد الأدنى لمتطلبات نظام البناء لمشاريع Python" . هذا ملف TOML ، والذي يمكن اعتباره نسخة أكثر تقدمًا من تنسيق ini ، حيث يتم تخزين المعلمات في شكل "key = value" ، ويمكن وضع المعلمات ليس فقط في أقسام مثل [foo] ، والتي تسمى الجداول في مصطلحات TOML ، ولكن وفي الأقسام الفرعية للنموذج [foo.bar.spam]. لا يمكن أن تحتوي المعلمات على سلاسل فقط ، بل تحتوي أيضًا على قوائم وأرقام وقيم منطقية.

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

يمكن العثور على وصف كامل لجميع المعلمات الممكنة لملف pyproject.toml المتعلقة بـ SIP على صفحة وثائق SIP .

على سبيل المثال ، قم بإنشاء ملف pyproject.toml بنفس مستوى مجلد foo :

foo_c_01 /
├── فو
├── ├── بن
│ │ ├── foo.o
│ │ ├── libfoo.a
│ │ ├── الرئيسي
│ │ └── main.o
│ ├── foo.c
│ ├── foo.h
├── ├── main.c
│ └── Makefile
yp pyproject.toml


ستكون محتويات pyproject.toml على النحو التالي:

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

إن قسم [نظام البناء] ("الجدول" بعبارات TOML) قياسي ويتم وصفه في PEP 518 . يحتوي على معلمتين:

  • يتطلب - قائمة الحزم اللازمة لبناء الحزمة الخاصة بنا. يتم وصف تنسيق وصف تبعية الحزمة في PEP 508 مواصفات التبعية لحزم برامج Python . في هذه الحالة ، نحتاج فقط إلى إصدار حزمة sip 5.x.
  • build-backend , . , Python-, . , , SIP, «sipbuild.api».

المعلمات الأخرى موصوفة في أقسام [tool.sip. *] . يحتوي

قسم [tool.sip.metadata] على معلومات عامة حول الحزمة: اسم الحزمة التي سيتم بناؤها ( ستسمى الحزمة لدينا pyfoo ، ولكن لا تخلط بين هذا الاسم واسم الوحدة ، والذي سنقوم لاحقًا باستيراده إلى Python) ، رقم إصدار الحزمة (في حالتنا رقم الإصدار "0.1") والترخيص (على سبيل المثال ، " MIT ").

يتم وصف أهم من وجهة نظر التجميع في [tool.sip.bindings. pyfoo ].

لاحظ اسم الحزمة في رأس القسم. لقد أضفنا معلمتين إلى هذا القسم:

  • الرؤوس - قائمة ملفات الرؤوس المطلوبة لاستخدام مكتبة foo.
  • مكتبات - قائمة ملفات الكائنات المترجمة لربط ثابت.
  • تشمل-تطبيق الاستعراض المفصل هو المسار حيث للبحث عن الملفات رأس إضافية إلى جانب تلك التي تعلق على مترجم C. في هذه الحالة، حيث أن نبحث عن foo.h ملف .
  • Library-dirs هو المسار الذي تبحث فيه عن ملفات كائن إضافية غير تلك المرفقة بمترجم C. في هذه الحالة ، هذا هو المجلد الذي يتم فيه إنشاء ملف مكتبة مترجم foo .

لذا ، أنشأنا أول ملف ضروري لـ SIP. ننتقل الآن إلى إنشاء الملف التالي الذي سيصف محتويات وحدة Python المستقبلية.

pyfoo.sip


قم بإنشاء ملف pyfoo.sip في نفس المجلد مثل ملف pyproject.toml :

foo_c_01 /
├── فو
├── ├── بن
│ │ ├── foo.o
│ │ ├── libfoo.a
│ │ ├── الرئيسي
│ │ └── main.o
│ ├── foo.c
│ ├── foo.h
├── ├── main.c
│ └── Makefile
├── pyfoo.sip
yp pyproject.toml


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

في مثالنا ، يجب تسمية هذا الملف pyfoo.sip ، لأنه قبل ذلك ، في ملف pyproject.toml ، أنشأنا [tool.sip.bindings. pyfoo]. في الحالة العامة ، يمكن أن يكون هناك العديد من هذه الأقسام ، وبالتالي ، يجب أن يكون هناك العديد من ملفات * .sip. ولكن إذا كان لدينا العديد من ملفات sip ، فهذه حالة خاصة من وجهة نظر SIP ، ولن نعتبرها في هذه المقالة. يرجى ملاحظة أنه في الحالة العامة ، قد لا يتطابق اسم ملف .sip (وبالتالي اسم القسم) مع اسم الحزمة المحددة في معلمة الاسم في قسم [tool.sip.metadata] .

ضع في اعتبارك ملف pyfoo.sip من مثالنا:

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

int foo(char*);

تسمى الخطوط التي تبدأ بالحرف "٪" بأوامر. يجب أن يخبروا SIP بكيفية تجميع ونمط وحدة Python بشكل صحيح. تم وصف قائمة كاملة بالتوجيهات في صفحة التوثيق هذه . بعض التوجيهات لها معلمات إضافية. قد لا تكون المعلمات مطلوبة.

في هذا المثال ، نستخدم توجيهين ؛ سنتعرف على بعض التوجيهات الأخرى في الأمثلة التالية. يبدأ

ملف pyfoo.sip بالأمر ٪ Module (name = foo، language = “C”) . يرجى ملاحظة أننا حددنا قيمة المعلمة الأولى ( الاسم ) بدون علامات اقتباس ، وقيمة المعلمة الثانية ( اللغة) مع علامات الاقتباس ، مثل السلاسل في C / C ++. هذا أحد متطلبات هذا التوجيه كما هو موضح في وثائق توجيه ٪ Module .

في توجيه ٪ Module ، مطلوب فقط معلمة الاسم ، والتي تحدد اسم وحدة Python التي سنستورد منها وظيفة المكتبة. في هذه الحالة ، تسمى الوحدة foo ، وستحتوي على الوظيفة foo ، لذلك بعد التجميع والتثبيت ، سنستوردها باستخدام الرمز:

from foo import foo

يمكننا جعل هذه الوحدة متداخلة في وحدة أخرى عن طريق استبدال هذا الخط ، على سبيل المثال ، بما يلي:

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

ثم يجب استيراد الوظيفة foo كما يلي:

from foo.bar import foo

تشير معلمة اللغة لتوجيه ٪ Module إلى اللغة التي تمت كتابة مكتبة المصدر بها. يمكن أن تكون قيمة هذه المعلمة إما "C" أو "C ++". إذا لم يتم تحديد هذه المعلمة ، فسيفترض SIP أن المكتبة مكتوبة بلغة C ++.

انظر الآن إلى السطر الأخير من ملف pyfoo.sip :

int foo(char*);

هذا وصف لواجهة الوظيفة من المكتبة التي نريد وضعها في وحدة Python. استنادًا إلى هذا الإعلان ، سيقوم sip بإنشاء دالة Python. أعتقد أن كل شيء يجب أن يكون واضحًا هنا.

نقوم بجمع والتحقق


الآن كل شيء جاهز لإنشاء حزمة Python مع ربط لمكتبة C. أولاً وقبل كل شيء ، تحتاج إلى بناء المكتبة نفسها. انتقل إلى مجلد pyfoo_c_01 / foo / وابدأ البناء باستخدام الأمر 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

إذا سار كل شيء على ما يرام ، فسيتم إنشاء مجلد bin داخل مجلد foo ، والذي سيكون فيه مكتبة libfoo.a مجمعة من بين الملفات الأخرى . دعني أذكرك أنه هنا ، حتى لا يتم تشتيت انتباهك عن الموضوع الرئيسي ، نحن نتحدث فقط عن البناء تحت Linux باستخدام gcc. ارجع إلى مجلد pyfoo_c_01 . حان الوقت الآن للتعرف على فرق SIP. بعد تثبيت SIP ، ستصبح أوامر سطر الأوامر التالية متاحة ( صفحة الوثائق ):



  • بناء رشفة . ينشئ ملف كائن ملحق Python.
  • تثبيت رشفة . إنشاء ملف كائن ملحق Python وتثبيته.
  • sip-sdist. .tar.gz, pip.
  • sip-wheel. wheel ( .whl).
  • sip-module. , , SIP. , , . , standalone project, , , , .
  • sip-distinfo. .dist-info, wheel.

يجب تشغيل هذه الأوامر من المجلد حيث يوجد ملف pyproject.toml .

للبدء ، لفهم عملية SIP بشكل أفضل ، قم بتشغيل الأمر sip-build ، مع خيار - verbose للحصول على إخراج أكثر تفصيلاً لوحدة التحكم ، ومعرفة ما يحدث أثناء عملية البناء.

$ sip-build - verbose

سيتم إنشاء هذه الارتباطات: pyfoo.
توليد روابط pyfoo ...
تجميع وحدة "foo" ...
بناء ملحق "foo"
إنشاء إنشاء
إنشاء build / temp.linux-x86_64-3.8
gcc -pthread -Wno-unused-result -Wsign-مقارنة -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -march = x86-64 -mtune = عام -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: في وظيفة
func_foo : sipfoocmodule .c: 29: 22: تحذير: إعلان دالة ضمني "foo" [-Wimplicit-function-signature]
29 | sipRes = foo (a0) ؛
| ^ ~~
gcc -pthread -Wno-unused-result -Wsign-مقارنة -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c array.c -o build / temp.linux-x86_64-3.8 / array.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c bool.cpp -o build / temp.linux-x86_64-3.8 / bool.o
gcc -pthread -Wno-unused-result -Wsign-مقارنة -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = عام -O3 -الأنابيب -fno-plt -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c objmap.c -o build / temp.linux-x86_64-3.8 / objmap.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c qtlib.c -o build / temp.linux-x86_64-3.8 / qtlib.o
gcc -pthread -Wno-unused-result -Wsign-مقارنة -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c int_convertors.c -o build / temp.linux-x86_64-3.8 / int_convertors.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c voidptr.c -o build / temp.linux-x86_64-3.8 / voidptr.o
gcc -pthread -Wno-unused-result -Wsign-مقارنة -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c apiversions.c -o build / temp.linux-x86_64-3.8 / apiversions.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c descriptors.c -o build / temp.linux-x86_64-3.8 / descriptors.o
gcc -pthread -Wno-unused-result -Wsign-مقارنة -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c المواضيع. c -o build / temp.linux-x86_64-3.8 / thread.o
gcc -pthread -Wno-unused-result -Wsign -compare -DNDEBUG -g -fwrapv -O3 -Wall -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fno-semantic-interposition -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -march = x86-64 -mtune = عام -O3 -pipe -fno-plt -fPIC -DSIP_PROTECTED_IS_PUBLIC -Dprotected = public -I. -I ../../ foo -I / usr / include / python3.8 -c siplib.c -o build / temp.linux-x86_64-3.8 / siplib.o
siplib.c: في الدالة "slot_richcompare":
siplib.c: 9536: 16: تحذير: يمكن استخدام "st" بدون تهيئة في هذه الوظيفة [-Wmaybe-uninitialized]
9536 | فتحة = findSlotInClass (ctd، st) ؛
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~
siplib.c: 10671: 19: ملاحظة: تم إعلان "st" هنا
10671 | sipPySlotType st ؛
| ^ ~
siplib.c: في الوظيفة "parsePass2":
siplib.c: 5625: 32: تحذير: يمكن استخدام "مالك" بدون تهيئة في هذه الوظيفة [-Wmaybe-uninitialized]
5625 | * owner = arg؛
| ~~~~~~~ ^ ~
g ++ -pthread -shared -Wl، -O1، - sort-common، - حسب الحاجة، -z، relro، -z، الآن -fno-semantic-interposition -Wl، -O1، - sort-الشائعة، - حسب الحاجة ، -z ، relro ، -z ، الآن 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 / thread.o build / temp.linux -x86_64-3.8 / siplib.o -L ../../ foo / bin -L / usr / lib -lfoo -o / home / jenyay / projects / soft / sip-أمثلة / pyfoo_c_01 / build / foo / foo. cpython-38-x86_64-linux-gnu.so
تم بناء المشروع.

لن نتعمق في عمل SIP ، ولكن يمكن رؤيته من الناتج الذي تجمعه بعض المصادر. يمكن رؤية هذه المصادر في مجلد الإنشاء / foo / الذي تم إنشاؤه بواسطة هذا الأمر :

pyfoo_c_01
├── بناء
│ └── foo
ivers ├── apiversions.c
├── ├── array.c
├── ├── array.h
│ ├── bool.cpp
├── ├── بناء
│ │ └── temp.linux-x86_64-3.8
ivers │ ├── apiversions.o
│ │ ├── array.o
│ │ ├── bool.o
│ │ ├── الواصفات. o
│ │ ├── int_convertors.o
│ │ ├── objmap.o
│ │ ├── qtlib.o
│ │ ├── sipfoocmodule.o
│ │ ├── siplib.o
│ │ ├── المواضيع. o
│ │ └── voidptr.o
cript ├── الواصفات.ج
│ ├── 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
├── ├── المواضيع. c
│ └── voidptr.c
├── فو
├── ├── بن
│ │ ├── foo.o
│ │ ├── libfoo.a
│ │ ├── الرئيسي
│ │ └── main.o
│ ├── foo.c
│ ├── foo.h
├── ├── main.c
│ └── Makefile
├── pyfoo.sip
yp pyproject.toml


ظهرت مصادر مساعدة في مجلد build / foo . بدافع الفضول ، دعنا نلقي نظرة على ملف sipfoocmodule.c ، لأنه يتعلق مباشرة بوحدة foo التي سيتم إنشاؤها:

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

إذا كنت تعمل مع Python / C API ، سترى وظائف مألوفة. انتبه بشكل خاص لوظيفة func_foo بدءًا من السطر 18.

نتيجة لتجميع هذه المصادر ، سيتم إنشاء ملف الإنشاء / foo / foo.cpython-38-x86_64-linux-gnu.so ، ويحتوي على ملحق Python ، الذي لا يزال بحاجة إلى تثبيته بشكل صحيح.

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

أولاً ، استخدم الأمر sip-sdist . استخدامه بسيط للغاية:

$ sip-sdist

The sdist has been built.

بعد ذلك ، سيتم إنشاء ملف pyfoo-0.1.tar.gz ، والذي يمكن تثبيته باستخدام الأمر:

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

ونتيجة لذلك ، سيتم عرض المعلومات التالية وسيتم تثبيت الحزمة:

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

دعونا نتأكد من أننا تمكنا من عمل ربط Python. نبدأ Python ونحاول استدعاء الوظيفة. دعني أذكرك أنه وفقًا لإعداداتنا ، تحتوي حزمة pyfoo على وحدة foo ، التي تحتوي على وظيفة foo .

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

يرجى ملاحظة أنه كمعلمة للدالة ، فإننا لا نمرر سلسلة فقط ، ولكن سلسلة من وحدات البايت b'123456 '- تناظرية مباشرة من char * إلى C. بعد ذلك بقليل ، سنضيف تحويل حرف * إلى str والعكس صحيح. كانت النتيجة متوقعة. دعني أذكرك بأن الدالة foo تُرجع الحجم المزدوج لصفيف من النوع char * ، الذي تم تمريره إليها كمعلمة.

لنحاول تمرير سلسلة Python عادية إلى دالة foo بدلاً من قائمة وحدات البايت.

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

تعذر على الربط الذي تم إنشاؤه تحويل السلسلة إلى char * ؛ سنناقش كيفية تعليمها كيفية القيام بذلك في القسم التالي.

تهانينا ، قمنا بعمل أول ربط على مكتبة مكتوبة في C.

اخرج من مترجم Python وقم بتجميع التجميع في شكل عجلة. كما تعلم على الأرجح ، فإن العجلة هي تنسيق حزمة جديد نسبيًا تم استخدامه عالميًا مؤخرًا. تم وصف التنسيق في PEP 427 ، "The Wheel Binary Package Format 1.0" ، ولكن وصف ميزات تنسيق العجلة هو موضوع يستحق مقالة كبيرة منفصلة. من المهم بالنسبة لنا أن يتمكن المستخدم من تثبيت الحزمة بسهولة في شكل عجلة باستخدام النقطة.

الحزمة في شكل عجلة ليست أكثر تعقيدًا من حزمة في تنسيق sdist. للقيام بذلك ، في المجلد مع الملفpyproject.toml بحاجة إلى تنفيذ الأمر

sip-wheel

بعد تشغيل هذا الأمر ، سيتم عرض عملية البناء وقد تكون هناك تحذيرات من المترجم:

$ sip-wheel

سيتم إنشاء هذه الروابط: pyfoo.
إنشاء روابط pyfoo ...
تجميع وحدة "foo" ...
sipfoocmodule.c: في وظيفة func_foo:
sipfoocmodule.c: 29: 22: تحذير: إعلان ضمني عن وظيفة foo [-Wimplicit-function-signature]
29 | sipRes = foo (a0) ؛
| ^ ~~
siplib.c: في الوظيفة "slot_richcompare":
siplib.c: 9536: 16: تحذير: "st" يمكن استخدامها بدون تهيئة في هذه الوظيفة [-Wmaybe-uninitialized]
9536 | فتحة = findSlotInClass (ctd، st) ؛
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~
siplib.c: 10671: 19: ملاحظة: تم الإعلان عن "st" هنا
10671 | sipPySlotType st ؛
| ^ ~
siplib.c: في الوظيفة "parsePass2":
siplib.c: 5625: 32: تحذير: يمكن استخدام "مالك" بدون تهيئة في هذه الوظيفة [-Wmaybe-uninitialized]
5625 | * owner = arg؛
| ~~~~~~~ ^ ~ ~
تم بناء العجلة.

عند اكتمال التجميع (يتم تجميع مشروعنا الصغير بسرعة) ، سيظهر ملف باسم pyfoo-0.1-cp38-cp38-manylinux1_x86_64.whl أو ما شابه في مجلد المشروع . قد يختلف اسم الملف الذي تم إنشاؤه وفقًا لنظام التشغيل وإصدار Python.

الآن يمكننا تثبيت هذه الحزمة باستخدام النقطة:

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

يتم استخدام خيار --upgrade هنا بحيث تحل النقطة محل وحدة pyfoo المثبتة مسبقًا.

علاوة على ذلك ، يمكن استخدام وحدة foo وحزمة pyfoo ، كما هو موضح أعلاه.

أضف قواعد التحويل إلى حرف *


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

يمكن أن يعمل التعليق التوضيحي كعلامة ، والتي يمكن أن تكون في مجموعة الحالة أو غير محددة ، على سبيل المثال: / ReleaseGIL / ، أو بعض التعليقات التوضيحية تحتاج إلى تعيين بعض القيم ، على سبيل المثال: / Encoding = "UTF-8" /. إذا كانت هناك حاجة إلى تطبيق العديد من التعليقات التوضيحية على كائن ما ، يتم فصلها بفواصل داخل الخطوط المائلة: / annotation_1، annotation_2 /.

في المثال التالي ، الموجود في المجلد pyfoo_c_02 ، أضف ملف معلمة التعليقات التوضيحية pyfoo.sip foo :

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

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

يشير التعليق الترميز إلى أي ترميز يجب تمرير السلسلة إلى الدالة. قد تكون قيم هذا التعليق التوضيحي: ASCII أو Latin-1 أو UTF-8 أو None. إذا كان ترميز والشرح غير محددة أو يساوي لا شيء ، ثم لا تعرض المعلمة من أجل وظيفة مثل هذه إلى أي ترميز ويتم تمريرها إلى وظيفة كما هو، ولكن في هذه الحالة يجب أن تكون المعلمة في التعليمات البرمجية بايثون من نوع بايت ، أي مجموعة من البايت ، كما رأينا في المثال السابق. إذا تم تحديد الترميز ، يمكن أن تكون هذه المعلمة سلسلة (اكتب str في Python). ترميز الشرح يمكن تطبيقه فقط على معلمات نوع شار ، تشار CONST ،تشار * أو كون شار * .

دعونا نتحقق من كيفية عمل وظيفة foo من وحدة foo الآن . للقيام بذلك ، كما كان من قبل ، يجب عليك أولاً تجميع مكتبة foo عن طريق استدعاء الأمر make داخل مجلد foo ، ثم استدعاء الأمر ، على سبيل المثال ، sip-wheel ، من مجلد أمثلة pyfoo_c_02 . سيتم إنشاء ملف pyfoo-0.2-cp38-cp38-manylinux1_x86_64.whl أو باسم مشابه ، والذي يمكن تعيينه باستخدام الأمر:

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

إذا سار كل شيء على ما يرام ، فابدأ مترجم Python وحاول استدعاء وظيفة foo بحجة سلسلة:

>>> from foo import foo

>>> foo(b'qwerty')
12

>>> foo('qwerty')
12

>>> foo('')
24

أولاً ، نتأكد من أن استخدام وحدات البايت لا يزال ممكنًا. بعد ذلك ، نتأكد من أنه يمكننا الآن تمرير وسيطات السلسلة إلى دالة foo أيضًا. لاحظ أن الدالة foo لوسيطة سلسلة بالحروف الروسية أعادت قيمة مضاعفة مثل السلسلة التي تحتوي على أحرف لاتينية فقط. حدث هذا بسبب حقيقة أن الدالة foo لا تحسب طول السلسلة بالأحرف (وتضاعفها) ، ولكن طول صفيف char * ، ومنذ ذلك الحين في ترميز UTF-8 تشغل الحروف الروسية 2 بايت ، ثم حجم صفيف char * بعد التحويل من اتضح أن سلاسل Python تكون ضعف الطول.

غرامة! قمنا بحل المشكلة بحجة الدالةfoo ، ولكن ماذا لو كان لدينا عشرات أو المئات من هذه الوظائف في مكتبتنا ، فهل عليك تحديد ترميز المعلمات لكل منها؟ غالبًا ما يكون الترميز المستخدم في البرنامج هو نفسه ، ولا يوجد هدف لوظائف مختلفة للإشارة إلى ترميزات مختلفة. في هذه الحالة ، من الممكن تحديد ترميز افتراضي في SIP ، وإذا كانت هناك حاجة إلى ترميز لبعض الوظائف الأخرى ، فيمكن إعادة تعريفه باستخدام التعليق التوضيحي للترميز .

لتعيين ترميز معلمات الدالة بشكل افتراضي ، يتم استخدام التوجيه ٪ DefaultEncoding . يظهر استخدامه في المثال الموجود في مجلد pyfoo_c_03 .

للاستفادة من التوجيه ٪ DefaultEncoding ، قم بتغيير الملف pyfoo.sip، الآن محتوياتها على النحو التالي:

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

int foo(char*);

الآن ، إذا كانت الوسيطة دالة من نوع char ، char * ، إلخ. إذا لم يكن هناك ترميز الشرح ، ثم يتم أخذ الترميز من ٪ DefaultEncoding التوجيه ، وإذا لم يكن، ثم لا يتم تنفيذ التحويل، وجميع المعلمات شار * ، الخ من الضروري نقل الخطوط وليس البايتات . يتم جمع

مثال من مجلد pyfoo_c_03 والتحقق منه بنفس الطريقة مثل مثال من مجلد pyfoo_c_02 .

باختصار حول project.py. التجميع الآلي


حتى الآن ، استخدمنا ملفين من أدوات المساعدة لإنشاء ربط Python - pyproject.toml و pyfoo.sip . الآن سوف نتعرف على ملف آخر من هذا القبيل ، والذي يجب أن يسمى project.py . باستخدام هذا النص البرمجي ، يمكننا التأثير في عملية إنشاء الحزمة الخاصة بنا. دعونا نقوم بأتمتة البناء. من أجل جمع الأمثلة pyfoo_c_01 - pyfoo_c_03 من الأقسام السابقة ، كان عليك أولاً الانتقال إلى مجلد foo / ، وتجميعها باستخدام الأمر make ، والعودة إلى المجلد حيث يوجد ملف pyproject.toml ، ثم البدء في بناء الحزمة باستخدام أحد أوامر sip- * .

الآن هدفنا هو التأكد من أنه عند تنفيذ أوامر sip-build و sip-sdist و sip-wheel ، يبدأ تجميع مكتبة foo C أولاً ، ثم يتم تشغيل الأمر نفسه بالفعل.

يوجد مثال تم إنشاؤه في هذا القسم في مجلد مصدر pyfoo_c_04 .

لتغيير عملية البناء ، يمكننا أن نعلن فئة في ملف project.py (يجب أن يكون اسم الملف هو فقط) مشتق من فئة sipbuild.Project . يحتوي هذا الفصل على طرق يمكننا تجاوزها بمفردنا. حاليًا نحن مهتمون بالطرق التالية:

  • بناء . يتم استدعاؤها أثناء الاستدعاء لأمر sip-build .
  • build_sdist . يتم استدعاؤه عند استدعاء الأمر sip-sdist .
  • عجلة_بنية . يتم استدعاؤه عند استدعاء أمر sip-wheel .
  • تثبيت . يتم استدعاؤه عند استدعاء أمر تثبيت sip .

أي أنه يمكننا إعادة تعريف سلوك هذه الأوامر. بالمعنى الدقيق للكلمة ، يتم الإعلان عن الأساليب المدرجة في sipbuild.Ab abstractProject من الفئة المجردة ، والتي يتم إنشاء sipbuild.Project من الفئة المشتقة منها .

قم بإنشاء ملف project.py بالمحتويات التالية:

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

أعلنا FooProject الطبقة ، والمستمدة من sipbuild.Project الطبقة ، و تعريف البناء ، build_sdist ، build_wheel، و تثبيت الأساليب في ذلك . في جميع هذه الطرق ، نطلق على الأساليب التي تحمل نفس الاسم من الفئة الأساسية ، قبل استدعاء الأسلوب _build_foo ، الذي يبدأ في تنفيذ الأمر make في مجلد foo .

لاحظ أنه يجب أن تُرجع طريقتا build_sdist و build_wheel اسم الملف الذي قاما بإنشائه. لم يتم كتابة هذا في الوثائق ، ولكن تم الإشارة إليه في مصادر SIP.

لا نحتاج الآن إلى تشغيل الأمر makeيدويًا لإنشاء مكتبة foo ، سيتم ذلك تلقائيًا.

إذا قمت الآن بتشغيل الأمر sip-wheel في مجلد pyfoo_c_04 ، فسيتم إنشاء ملف باسم pyfoo-0.4-cp38-cp38-manylinux1_x86_64.whl أو ما شابه ذلك وفقًا لنظام التشغيل وإصدار Python. يمكن تثبيت هذه الحزمة باستخدام الأمر:



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

بعد ذلك ، يمكنك التأكد من أن وظيفة foo من وحدة foo لا تزال تعمل.

أضف خيارات سطر الأوامر للبناء


المثال التالي موجود في مجلد pyfoo_c_05 ، وتحتوي الحزمة على رقم إصدار 0.5 (انظر الإعدادات في ملف pyproject.toml ). يعتمد هذا المثال على مثال من الوثائق مع بعض التصحيحات. في هذا المثال ، سنعيد ملف project.py الخاص بنا ونضيف خيارات سطر أوامر جديدة للبناء.

في الأمثلة لدينا، ونحن نعمل حاليا على بناء مكتبة بسيط جدا فو، وفي المشاريع الحقيقية ، يمكن أن تكون المكتبة كبيرة جدًا ومن ثم لن يكون من المنطقي إدراجها في شفرة المصدر لمشروع ربط Python. دعني أذكرك أن SIP تم إنشاؤه في الأصل لإنشاء ارتباط لمكتبة ضخمة مثل Qt. يمكنك بالطبع أن تجادل بأن الوحدات الفرعية من git يمكن أن تساعد في تنظيم المصدر ، لكن هذه ليست النقطة. افترض أن المكتبة قد لا تكون في المجلد بمصدر الربط. في هذه الحالة ، يطرح السؤال ، أين يجب أن يبحث جامع SIP عن رأس المكتبة وملفات الكائن؟ في هذه الحالة ، قد يكون لدى مستخدمين مختلفين طرقهم الخاصة لوضع المكتبة.

لحل هذه المشكلة ، سنضيف خيارين جديدين لسطر الأوامر إلى نظام البناء ، يمكنك من خلالهما تحديد المسار إلى الملف foo.h (المعلمة - foo-include-dir) وإلى ملف كائن المكتبة (المعلمة - foo-library-dir ). بالإضافة إلى ذلك ، سنفترض أنه إذا لم يتم تحديد هذه المعلمات ، فستظل مكتبة foo موجودة جنبًا إلى جنب مع مصادر الربط.

نحتاج إلى إنشاء ملف project.py مرة أخرى ، ونعلن فيه فئة مشتقة من sipbuild.Project . دعنا نلقي نظرة على الإصدار الجديد من ملف project.py أولاً ، ثم نرى كيف يعمل.

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)

أنشأنا مرة أخرى فئة FooProject ، المشتقة من sipbuild.Project . في هذا المثال ، تم تعطيل التجميع التلقائي لمكتبة foo ، لأنه من المفترض الآن أنه يمكن أن يكون في مكان آخر ، وبحلول وقت إنشاء الربط ، يجب أن تكون ملفات الرأس والكائن جاهزة. يتم إعادة تعريف ثلاث طرق

في FooProject الدرجة : get_options ، apply_user_defaults، و التحديث . اعتبرهم بعناية أكبر.

لنبدأ مع طريقة get_options . يجب أن ترجع هذه الطريقة قائمة مثيلات فئة sipbuild.Option. كل عنصر قائمة هو خيار سطر الأوامر. داخل الطريقة التي تم تجاوزها ، نحصل على قائمة الخيارات الافتراضية (متغير الخيارات ) عن طريق استدعاء طريقة الفئة الأساسية التي تحمل نفس الاسم ، ثم نقوم بإنشاء خيارين جديدين ( --foo_include_dir و --foo_library_dir ) ونضيفهما إلى القائمة ، ثم نعيد هذه القائمة من الوظيفة. يقبل

مُنشئ فئة الخيار معلمة واحدة مطلوبة (اسم الخيار) وعددًا كبيرًا بما يكفي من المعلمات الاختيارية التي تصف نوع القيمة لهذه المعلمة والقيمة الافتراضية ووصف المعلمة وبعض المعلمات الأخرى. يستخدم هذا المثال الخيارات التالية لمنشئ الخيار :

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

تم تصميم طريقة application_user_defaults الزائدة التالية لتعيين قيم المعلمات التي يمكن للمستخدم تمريرها عبر سطر الأوامر. تنشئ طريقة application_user_defaults من الفئة الأساسية متغيرًا (عضو فئة) لكل معلمة سطر أوامر تم إنشاؤها في طريقة get_options ، لذا من المهم استدعاء طريقة الفئة الأساسية التي تحمل نفس الاسم قبل استخدام المتغيرات التي تم إنشاؤها بحيث يتم إنشاء جميع المتغيرات التي تم إنشاؤها باستخدام معلمات سطر الأوامر وتهيئتها باستخدام القيم الافتراضية . بعد ذلك ، في مثالنا ، سيتم إنشاء المتغيرات self.foo_include_dir و self.foo_library_dir. إذا لم يحدد المستخدم معلمات سطر الأوامر المقابلة ، فسيأخذ القيم الافتراضية وفقًا لمعلمات مُنشئ فئة الخيار (المعلمة الافتراضية ). إذا لم يتم تعيين المعلمة الافتراضية ، فاعتمادًا على نوع قيمة المعلمة المتوقعة ، ستتم تهيئتها إما إلى لا شيء ، أو إلى قائمة فارغة ، أو 0.

داخل طريقة Apply_user_defaults ، تأكد من أن المسارات في المتغيرات self.foo_include_dir و self.foo_library_dir دائمًا ما تكون مطلقة. يعد ذلك ضروريًا بحيث لا يعتمد على مجلد العمل في وقت بدء التجميع.

آخر طريقة مثقلة في هذه الفئة هو التحديث.. يتم استدعاء هذه الطريقة عندما يكون من الضروري تطبيق التغييرات التي تم إجراؤها قبل ذلك على المشروع. على سبيل المثال ، قم بتغيير أو إضافة المعلمات المحددة في ملف pyproject.toml . في الأمثلة السابقة ، قمنا بتعيين المسارات إلى ملفات الرأس والكائن باستخدام معلمات include-dirs و library-dirs ، على التوالي ، داخل قسم [tool.sip.bindings.pyfoo] . الآن سنقوم بتعيين هذه المعلمات من البرنامج النصي project.py ، لذلك في ملف pyproject.toml سنقوم بإزالة هذه المعلمات:

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

داخل طريقة تحديث لنا من القاموس self.bindings مرتبطا pyfoo جات المثال sipbuild.Bindings . يتوافق اسم المفتاح مع [tool.sip.bindings. pyfoo ] من ملف pyproject.toml ، وبالتالي يصف مثيل الفئة الذي تم الحصول عليه الإعدادات الموضحة في هذا القسم. ثم ، أعضاء هذه الفئة include_dirs و library_dirs (تتوافق أسماء الأعضاء مع المعلمات تشمل - dirs و dirs-library مع واصلة يتم استبدالها بشرطة سفلية) يتم تعيين قوائم تحتوي على المسارات المخزنة في self.foo_include_dir و self.foo_library_dir. في هذا المثال ، من أجل الدقة ، نتحقق من أن القيم self.foo_include_dir و self.foo_library_dir لا تساوي لا شيء ، ولكن في هذا المثال يتم استيفاء هذا الشرط دائمًا لأن معلمات سطر الأوامر التي أنشأناها تحتوي على قيم افتراضية.

وبالتالي ، قمنا بإعداد ملفات التكوين بحيث كان من الممكن أثناء التجميع الإشارة إلى المسارات إلى ملفات الرأس والكائن. دعونا نتحقق مما حدث.

أولاً ، تأكد من عمل القيم الافتراضية. للقيام بذلك ، انتقل إلى مجلد pyfoo_c_05 / foo وقم ببناء المكتبة باستخدام الأمر make ، لأننا قمنا بتعطيل بناء المكتبة التلقائي في هذا المثال.

بعد ذلك ، انتقل إلى المجلدpyfoo_c_05 وقم بتشغيل الأمر sip-wheel . نتيجة لهذا الأمر ، سيتم إنشاء ملف pyfoo-0.5-cp38-cp38-manylinux1_x86_64.whl أو باسم مشابه.

الآن قم بنقل مجلد foo إلى مكان ما خارج مجلد pyfoo_c_05 وقم بتشغيل الأمر sip-wheel مرة أخرى . ونتيجة لذلك ، نحصل على الخطأ المتوقع بإبلاغنا بأنه ليس لدينا ملف مكتبة كائنات:

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

بعد ذلك ، قم بتشغيل sip-wheel باستخدام خيار سطر الأوامر الجديد:

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

بدلاً من الحذف ، تحتاج إلى تحديد المسار إلى المجلد حيث قمت بنقل مجلد foo مع المكتبة المجمعة. نتيجة لذلك ، يجب أن ينجح التجميع في إنشاء ملف .whl. يمكن تثبيت الوحدة النمطية التي تم إنشاؤها واختبارها بنفس الطريقة كما في الأقسام السابقة.

تحقق من ترتيب طرق الاتصال من project.py


المثال التالي ، الذي سننظر فيه ، سيكون بسيطًا للغاية ؛ سيوضح ترتيب استدعاء أساليب فئة المشروع ، والتي قمنا بتحميلها بشكل زائد في الأقسام السابقة. يمكن أن يكون هذا مفيدًا لفهم متى يمكن تهيئة المتغيرات. يقع هذا المثال في مجلد pyfoo_c_06 في مستودع المصدر.

جوهر هذا المثال هو زيادة التحميل على جميع الطرق التي استخدمناها من قبل في فئة FooProject ، الموجودة في ملف project.py ، وإضافة مكالمات إلى وظيفة الطباعة لهم ، والتي تعرض اسم الطريقة التي توجد فيها:

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

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

في ملف pyproject.toml ، أعد المسار الصريح إلى المكتبة:

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

لبناء المشروع بنجاح ، تحتاج إلى الانتقال إلى مجلد foo وإنشاء المكتبة هناك باستخدام الأمر make . بعد ذلك ، ارجع إلى مجلد pyfoo_c_06 وقم بتشغيل الأمر sip-wheel ، على سبيل المثال . ونتيجة لذلك ، إذا تجاهلت تحذيرات المترجم ، فسيتم عرض النص التالي:

get_options ()
application_nonuser_defaults ()
get_options ()
get_options ()
application_user_defaults ()
get_options ()
update ()
سيتم إنشاء هذه الروابط: pyfoo.
build_wheel ()
توليد روابط pyfoo ...
تجميع وحدة "foo" ...
تم بناء العجلة.

يتم عرض الخطوط الغامقة التي يتم إخراجها من ملف project.py الخاص بنا . وبالتالي ، نرى أن طريقة get_options تسمى عدة مرات ، ويجب أخذ ذلك في الاعتبار إذا كنت ستقوم بتهيئة أي متغير عضو في الفصل مشتق من Project . طريقة get_options ليست المكان الأفضل لذلك.

من المفيد أيضًا أن تتذكر أن طريقة application_nonuser_defaults يتم استدعاؤها قبل طريقة application_user_defaults ، أي في طريقة application_user_defaults ، من الممكن بالفعل استخدام المتغيرات التي تم تعيين قيمها في طريقة application_nonuser_defaults .

بعد ذلك ، يتم استدعاء طريقة التحديث، وفي النهاية ، الطريقة المسؤولة مباشرة عن التجميع ، في حالتنا ، build_wheel .

خاتمة الجزء الأول


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

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

في ملف * .sipيصف واجهة وحدة Python التي تسرد الوظائف والفئات التي سيتم تضمينها في الوحدة. تُستخدم التوجيهات والتعليقات التوضيحية لوصف الواجهة في ملف * .sip .

في الجزء الثاني من المقالة ، سننشئ ربطًا على مكتبة موجهة للكائنات مكتوبة بلغة C ++ ، وسنقوم من خلال مثالنا بدراسة التقنيات التي ستكون مفيدة في وصف واجهة فئات C ++ ، وفي نفس الوقت سنتعامل مع توجيهات وشروح جديدة.

يتبع.

المراجع



All Articles