برمجة الامتدادات في بايثون بيثون Python Extension Programming with C


برمجة الامتدادات في بايثون بيثون

 Python  Extension Programming with C


برمجة الامتدادات في بايثون بيثون Python  Extension Programming with C



يمكن دمج أي رمز تكتبه باستخدام أي لغة مترجمة مثل C أو C ++ أو Java أو استيرادها في برنامج نصي Python آخر.  يعتبر هذا الرمز بمثابة "ملحق".

 وحدة امتداد Python ليست أكثر من مكتبة C عادية.  على أجهزة Unix ، عادة ما تنتهي هذه المكتبات بـ .so (للكائن المشترك).  على أجهزة Windows ، تشاهد عادةً .dll (للمكتبة المرتبطة ديناميكيًا).


Pre-Requisites for Writing Extensions

المتطلبات الأساسية لكتابة ملحقات
لبدء كتابة ملحقك ، ستحتاج إلى ملفات رأس Python.

 على أجهزة Unix ، يتطلب هذا عادةً تثبيت حزمة خاصة بالمطور مثل python2.5-dev.

 يحصل مستخدمو Windows على هذه الرؤوس كجزء من الحزمة عندما يستخدمون مثبّت Python الثنائي.

 بالإضافة إلى ذلك ، يُفترض أن لديك معرفة جيدة بـ C أو C ++ لكتابة أي امتداد Python باستخدام البرمجة C.

First look at a Python Extens

نظرة أولى  على بناء الامتدادات بيثون 

لإلقاء نظرة أولى على وحدة امتداد Python ، تحتاج إلى تجميع الكود في أربعة أجزاء -

 ملف الرأس Python.h.
 وظائف C التي تريد كشفها كواجهة من الوحدة النمطية الخاصة بك.
 جدول يعين أسماء وظائفك كما يراه مطورو بيثون في وظائف C داخل وحدة الامتداد.
 وظيفة التهيئة.

The Header File Python.h ملف الرأس أو الرئيسي في بايثون بيثون 

تحتاج إلى تضمين ملف رأس Python.h في الملف المصدر C الخاص بك ، والذي يتيح لك الوصول إلى API Python الداخلية المستخدمة لربط الوحدة النمطية الخاصة بك في مترجم.

 تأكد من تضمين Python.h قبل أي رؤوس أخرى قد تحتاجها.  تحتاج إلى متابعة الإضافات مع الوظائف التي تريد الاتصال بها من بيثون.

The C Functions الدوال في لغة سي

تواقيع تنفيذ C لوظائفك تأخذ دائمًا أحد الأشكال الثلاثة التالية -
static PyObject *MyFunction( PyObject *self, PyObject *args );

static PyObject *MyFunctionWithKeywords(PyObject *self,
                                 PyObject *args,
                                 PyObject *kw);

static PyObject *MyFunctionWithNoArgs( PyObject *self );

كل واحد من التصريحات السابقة بإرجاع كائن Python.  لا يوجد شيء باسم دالة باطلة في Python كما هو الحال في C. إذا كنت لا تريد أن تُرجع دالاتك قيمة ، فقم بإرجاع C المكافئ لقيمة None Python.  تحدد رؤوس Python ماكرو ، Py_RETURN_NONE ، يقوم بذلك لنا.

 يمكن أن تكون أسماء وظائف C الخاصة بك كما تريد لأنها لا تُرى خارج وحدة الامتداد.  يتم تعريفها على أنها وظيفة ثابتة.

 عادة ما يتم تسمية وظائف C الخاصة بك عن طريق الجمع بين الوحدة النمطية Python وأسماء الوظائف معًا ، كما هو موضح هنا -
static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Do your stuff here. */
   Py_RETURN_NONE;
}
هذه هي وظيفة بيثون تسمى فونك داخل وحدة الوحدة النمطية.  ستضع مؤشرات على وظائف C الخاصة بك في جدول الطريقة للوحدة النمطية التي عادة ما تأتي بعد ذلك في التعليمات البرمجية المصدر.

The Method Mapping Table

جدول تعيين الدالة 

جدول الطريقة هذا عبارة عن مجموعة بسيطة من هياكل PyMethodDef.  يبدو هذا الهيكل شيئًا كهذا -
struct PyMethodDef {
   char *ml_name;
   PyCFunction ml_meth;
   int ml_flags;
   char *ml_doc;
};
هنا هو وصف أعضاء هذا الهيكل -

 ml_name - هذا هو اسم الوظيفة التي يقدمها مترجم Python عند استخدامها في برامج Python.

 ml_meth - يجب أن يكون هذا هو عنوان دالة بها أي من التوقيعات الموضحة في القسم السابق.

 ml_flags - هذا يخبر المترجم بأي من التواقيع الثلاثة التي يستخدمها ml_meth.

 تحتوي هذه العلامة عادةً على قيمة METH_VARARGS.

 يمكن أن تكون هذه العلامة ORwise مع METH_KEYWORDS إذا كنت تريد السماح لوسائط الكلمات الرئيسية في وظيفتك.

 يمكن أن يكون لهذا أيضًا قيمة METH_NOARGS التي تشير إلى أنك لا تريد قبول أي وسيطات.

 ml_doc - هذا هو docstring للوظيفة ، والتي يمكن أن تكون NULL إذا كنت لا ترغب في كتابة واحدة.

 يحتاج هذا الجدول إلى إنهائه بحارس يتكون من NULL و 0 قيم للأعضاء المناسبين.

Example مثال


بالنسبة للوظيفة المحددة أعلاه ، لدينا جدول تعيين الأسلوب التالي -
static PyMethodDef module_methods[] = {
   { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
   { NULL, NULL, 0, NULL }
};

The Initialization Function دالة التهيئة 

الجزء الأخير من وحدة الامتداد الخاصة بك هو وظيفة التهيئة.  يتم استدعاء هذه الوظيفة بواسطة مترجم Python عند تحميل الوحدة.  يجب تسمية الدالة initModule ، حيث الوحدة هي اسم الوحدة النمطية.

 يجب أن يتم تصدير وظيفة التهيئة من المكتبة التي ستقوم ببنائها.  تعرّف رؤوس Python PyMODINIT_FUNC لتضمين التعويذات المناسبة لذلك لكي تحدث في بيئة معينة نجمعها.  كل ما عليك فعله هو استخدامه عند تحديد الوظيفة.

 تحتوي وظيفة تهيئة C بشكل عام على البنية العامة التالية -
PyMODINIT_FUNC initModule() {
   Py_InitModule3(func, module_methods, "docstring...");
}
هنا وصف وظيفة Py_InitModule3 -

 func - هذه هي الوظيفة التي سيتم تصديرها.

 module_methods - هذا هو اسم جدول التعيين المحدد أعلاه.

 docstring - هذا هو التعليق الذي تريد تقديمه في امتدادك.

 يبدو وضع هذا معًا كما يلي -
#include 

static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Do your stuff here. */
   Py_RETURN_NONE;
}

static PyMethodDef module_methods[] = {
   { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
   { NULL, NULL, 0, NULL }
};

PyMODINIT_FUNC initModule() {
   Py_InitModule3(func, module_methods, "docstring...");
}

Example مثال

مثال بسيط يستخدم كل المفاهيم أعلاه -
#include 

static PyObject* helloworld(PyObject* self) {
   return Py_BuildValue("s", "Hello, Python extensions!!");
}

static char helloworld_docs[] =
   "helloworld( ): Any message you want to put here!!\n";

static PyMethodDef helloworld_funcs[] = {
   {"helloworld", (PyCFunction)helloworld, 
      METH_NOARGS, helloworld_docs},
      {NULL}
};

void inithelloworld(void) {
   Py_InitModule3("helloworld", helloworld_funcs,
                  "Extension module example!");
}
هنا يتم استخدام الدالة Py_BuildValue لإنشاء قيمة Python.  احفظ الكود أعلاه في ملف hello.c.  سنرى كيفية تجميع هذه الوحدة وتثبيتها ليتم استدعاؤها من البرنامج النصي Python.

Building and Installing Extensionsبناء وتركيب الامتدادات

تجعل حزمة distutils من السهل جدًا توزيع وحدات Python ، كل من وحدات Python النقية ووحدات الامتداد ، بطريقة قياسية.  يتم توزيع الوحدات النمطية في النموذج المصدر ويتم إنشاؤها وتثبيتها عبر برنامج نصي لإعداد يسمى عادةً setup.py على النحو التالي.

 بالنسبة للوحدة أعلاه ، تحتاج إلى إعداد البرنامج النصي setup.py التالي -
from distutils.core import setup, Extension
setup(name='helloworld', version='1.0',  \
      ext_modules=[Extension('helloworld', ['hello.c'])])
الآن ، استخدم الأمر التالي ، الذي سيؤدي جميع خطوات التحويل والربط الضرورية ، مع أوامر وأعلام المترجم والرابط الصحيحة ، وانسخ المكتبة الديناميكية الناتجة إلى دليل مناسب -
$ python setup.py install
في الأنظمة التي تستند إلى Unix ، ستحتاج على الأرجح إلى تشغيل هذا الأمر كجذر للحصول على أذونات للكتابة إلى دليل حزم المواقع.  هذا عادة لا يمثل مشكلة في ويندوز.

Importing Extensions استيراد الامتدادات

بمجرد تثبيت الامتداد الخاص بك ، ستتمكن من استيراد هذا الامتداد والاتصال به في البرنامج النصي Python الخاص بك كما يلي -
#!/usr/bin/python
import helloworld

print helloworld.helloworld()

هذا من شأنه أن ينتج النتيجة التالية -
Hello, Python extensions!!

Passing Function Parametersتمرير متغيرات الدالة 

إذا كنت ترغب في تحديد الوظائف التي تقبل الوسائط على الأرجح ، يمكنك استخدام أحد التواقيع الأخرى لوظائف C.  على سبيل المثال ، الوظيفة التالية ، التي تقبل عددًا من المعلمات ، سيتم تعريفها على النحو التالي -
static PyObject *module_func(PyObject *self, PyObject *args) {
   /* Parse args and do something interesting here. */
   Py_RETURN_NONE;
}
يبدو جدول الطريقة الذي يحتوي على إدخال للوظيفة الجديدة كما يلي -

static PyMethodDef module_methods[] = {
   { "func", (PyCFunction)module_func, METH_NOARGS, NULL },
   { "func", module_func, METH_VARARGS, NULL },
   { NULL, NULL, 0, NULL }
};
يمكنك استخدام دالة API PyArg_ParseTuple لاستخراج الوسائط من مؤشر PyObject واحد الذي تم تمريره إلى دالة C.

 الوسيطة الأولى إلى PyArg_ParseTuple هي الوسيطة args.  هذا هو الكائن الذي سيتم تحليله.  الوسيطة الثانية عبارة عن سلسلة تنسيق تصف الوسائط كما تتوقع ظهورها.  يتم تمثيل كل وسيطة بحرف واحد أو أكثر في سلسلة التنسيق كما يلي.
static PyObject *module_func(PyObject *self, PyObject *args) {
   int i;
   double d;
   char *s;

   if (!PyArg_ParseTuple(args, "ids", &i, &d, &s)) {
      return NULL;
   }
   
   /* Do something interesting here. */
   Py_RETURN_NONE;
}
تتيح لك ترجمة الإصدار الجديد من الوحدة النمطية الخاصة بك واستيرادها إمكانية استدعاء الوظيفة الجديدة بأي عدد من الوسائط من أي نوع -
module.func(1, s="three", d=2.0)
module.func(i=1, d=2.0, s="three")
module.func(s="three", d=2.0, i=1)
ربما يمكنك التوصل إلى مزيد من الاختلافات.

 وظيفة PyArg_ParseTuple
 هنا هو التوقيع الافتراضي لوظيفة PyArg_ParseTuple -
int PyArg_ParseTuple(PyObject* tuple,char* format,...)
هذه الدالة تقوم بإرجاع 0 للأخطاء ، وقيمة لا تساوي 0 للنجاح.  tuple هي PyObject * التي كانت الوسيطة الثانية للدالة C.  التنسيق هنا عبارة عن سلسلة C تصف الوسائط الإلزامية والاختيارية.

 فيما يلي قائمة برموز التنسيق لوظيفة PyArg_ParseTuple -
الرمزC نوعالمعنى
ccharسلسلة بيثون بطول 1 تصبح C char.
ddoubleA Python float becomes a C double.
ffloatA Python float becomes a C float.
iintA Python int becomes a C int.
llongA Python int becomes a C long.
Llong longA Python int becomes a C long long
OPyObject*Gets non-NULL borrowed reference to Python argument.
schar*Python string without embedded nulls to C char*.
s#char*+intAny Python string to C address and length.
t#char*+intRead-only single-segment buffer to C address and length.
uPy_UNICODE*Python Unicode without embedded nulls to C.
u#Py_UNICODE*+intAny Python Unicode C address and length.
w#char*+intRead/write single-segment buffer to C address and length.
zchar*Like s, also accepts None (sets C char* to NULL).
z#char*+intLike s#, also accepts None (sets C char* to NULL).
(...)as per ...A Python sequence is treated as one argument per item.
|The following arguments are optional.
:Format end, followed by function name for error messages.
;Format end, followed by entire error message text.

Returning Values قيم ذات المرجع

يأخذ Py_BuildValue في سلسلة تنسيق يشبه إلى حد كبير PyArg_ParseTuple.  بدلاً من تمرير عناوين القيم التي تنشئها ، يمكنك تمرير القيم الفعلية.  إليك مثال يوضح كيفية تنفيذ وظيفة الإضافة -
static PyObject *foo_add(PyObject *self, PyObject *args) {
   int a;
   int b;

   if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
      return NULL;
   }
   return Py_BuildValue("i", a + b);
}

هذا ما سيبدو إذا تم تنفيذه في بيثون -
def add(a, b):
   return (a + b)
يمكنك إرجاع قيمتين من وظيفتك على النحو التالي ، سيتم إيقاف هذا باستخدام قائمة في Python.
static PyObject *foo_add_subtract(PyObject *self, PyObject *args) {
   int a;
   int b;

   if (!PyArg_ParseTuple(args, "ii", &a, &b)) {
      return NULL;
   }
   return Py_BuildValue("ii", a + b, a - b);
}
هذا ما سيبدو إذا تم تنفيذه في بيثون -
def add_subtract(a, b):
   return (a + b, a - b)

The Py_BuildValue Function دالة Py_BuildValue

هنا هو التوقيع الافتراضي لوظيفة Py_BuildValue -
PyObject* Py_BuildValue(char* format,...)
التنسيق هنا عبارة عن سلسلة C تصف كائن Python المراد إنشاؤه.  الوسيطات التالية لـ Py_BuildValue هي قيم C مبنية عليها النتيجة.  نتيجة PyObject * هي مرجع جديد.

 يسرد الجدول التالي سلاسل الأكواد الشائعة الاستخدام ، والتي يتم ربط صفر أو أكثر بتنسيق السلسلة.
الكودC typeالمعنى
ccharC
char يصبح سلسلة بيثون بطول 1.
ddouble c
يصبح نوع بيانات كسري كبير بيثون.
ffloatc float becomes a Python float.
iintA C int becomes a Python int.
llongA C long becomes a Python int.
NPyObject*Passes a Python object and steals a reference.
OPyObject*Passes a Python object and INCREFs it as normal.
O&convert+void*Arbitrary conversion
schar*C 0-terminated char* to Python string, or NULL to None.
s#char*+intC char* and length to Python string, or NULL to None.
uPy_UNICODE*C-wide, null-terminated string to Python Unicode, or NULL to None.
u#Py_UNICODE*+intC-wide string and length to Python Unicode, or NULL to None.
w#char*+intRead/write single-segment buffer to C address and length.
zchar*Like s, also accepts None (sets C char* to NULL).
z#char*+intLike s#, also accepts None (sets C char* to NULL).
(...)as per ...Builds Python tuple from C values.
[...]as per ...Builds Python list from C values.
{...}as per ...Builds Python dictionary from C values, alternating keys and values.
يقوم الرمز {...} بإنشاء قواميس من عدد زوجي من قيم C والمفاتيح والقيم بالتناوب.  على سبيل المثال ، ترجع Py_BuildValue ("{issi}" ، 23 ، "zig" ، "zag" ، 42) قاموسًا مثل Python's {23: 'zig' ، 'zag': 42}.