Extending Python with C
الموضوع خاضع للرخصة السفاحية
وإهداء ل صوفى وستورم وجراى
ماهى ال Extensions ؟
هى امتدادات لل Python مكتوبة بال C او C-Like مثل ال C++ على سبيل المثال
سهل كتابة extensions لل Python ولكن مالهدف ؟
الهدف إنك تضيف built-in modules لل Python مكتوبة بال C بهدف السرعة مثلا او إضافة built-in types او عمل encapsulation لل C lib functions او System Calls او حتى إخفاء ال source code الخاص بيك

المتطلبات: خبرة جيدة بال C و Python
اول شئ بكل تأكيد هو إننا هنستخدم Python API/C وذلك بضم python.h لل project
ملحوظة: قم بضم python.h قبل اى header اخر. جميل نبدأ ب Hello, World

1- انشئ ملف helloMod.c
2- اكتب التالى
كود:
#include <Python.h> static PyObject* hola(PyObject* self, PyObject* args) { if (!PyArg_ParseTuple(args, "", NULL)) return NULL; printf("Hola!"); Py_RETURN_NONE; } static PyMethodDef HolaMethods[] = { {"hola", hola, METH_VARARGS, "prints Hola\n"}, {NULL, NULL, 0, NULL} }; PyMODINIT_FUNC inithola(void) { (void) Py_InitModule("hola", HolaMethods); }
اولا نعمل include ل python api header كالتالى
كود:
#include <Python.h>
احنا الأول عايزين نعرف function بسيطة تطبع كلمة Hola!
كود:
static PyObject* hola(PyObject* self, PyObject* args)
كود:
>>> hola.hola() Hola!
فالأول نختبر هل فى arguments اتباصت لل Function او لأ
كود:
if (!PyArg_ParseTuple(args, "", NULL)) return NULL;
args هى ال arguments اللى هتتباصى لل Function
هى بتعبر عن ال Data Type الخاص بال Argument مثلا s او i وهكذا
s: String
i: Integer
.. etc
NULL هنا بيعبر عن ال متغيرات اللى هتاخد القيم اللى اتباصت لل args -هنطلع عليها اكثر فى مثال قادم-
كود:
return NULL;
بعد كدا نيجى لل ال Function هتعمله وهو طباعة كلمة Hola بإستخدام printf
printf("Hola!");
واخيرا زى ماقلنا اننا مش عايزين اى return من ال Function فهنعمل Py_RETURN_NONE
كود:
Py_RETURN_NONE;
كود:
static PyMethodDef HolaMethods[] = { {"hola", hola, METH_VARARGS, "prints Hola"}, {NULL, NULL, 0, NULL} };
التانى هو ال function نفسها
التالت بيعبر عن ان ال Arguments اللى هتتباصى هى Python-Level Arguments وغالبا بنستخدم METH_VARAGS
الرابع هو الوصف الخاص بال function
ونحجز المكان التانى فى ال Array ب
كود:
{NULL, NULL, 0, NULL}
كود:
PyMODINIT_FUNC inithola(void) { (void) Py_InitModule("hola", HolaMethods); }
PyMODINIT_FUNC هى إختصار ل Python Module Initializer Function و فيها بيتم تجهيز ال Module
بإستدعاء Py_InitModule وهى Function وهى اللى بتقوم بالتجهيز بالفعل وبتاخد 2Arguments
1- اسم الModule
2- ال Methods Table
جميل جدا.. كدا كتبنا اول Module خاصة بينا!
هنحتاج نضم ال Extension لل Python ولكن إزاى ؟
بكل بساطة افتح ال Editor المفضل عندك وهنعمل setup script بال Python بإستخدام Distutils
كود:
from distutils.core import setup, Extension modExt = Extension('hola', sources = ['hola.c']) setup (name = 'HolaPackage', version = '1.0', description = 'Simple demo', ext_modules = [modExt])
1- إستدعينا setup, Extension من Disutils.core
2- عملنا Extension Object كالتالى
كود:
modExt = Extension('hola', sources = ['hola.c'])
3- تجهيز ال setup script بإننا نسجل فيه إسم ال Package و الإصدار والوصف و ال extensions كالتالى
كود:
setup (name = 'HolaPackage', version = '1.0', description = 'Simple demo', ext_modules = [modExt])
كود:
Python setup.py build
كود:
Python setup.py install
نجرب ال Hola Module كالتالى
1- اعمل اى Test Script وليكن HolaTest.py
2- اعمل import ل hola Module كالتالى
كود:
import hola
كود:
hola.hola()
#output:
Hola!
Hola!
كود:
print hola.hola()
#Output:
Hola!
None
Hola!
None
الفكرة بإختصار:
1- نعرف ال Functions
2- نضمهم لل Methods Table
3- نعمل Initialize لل Module
4- نجهز ال setup script
5- نعمل Build و Install لل Module
6- نستخدم ال Module عن طريق TestScript مثلا كالتالى
1- تعريف ال Functions
ال Hola Function
كود:
static PyObject* hola(PyObject* self, PyObject* args) { const char* name; int age; if (!PyArg_ParseTuple(args, "si", &name, &age)) return NULL; printf("Name: %s", name); printf("Age: %i", age); Py_RETURN_NONE; }
كود:
if (!PyArg_ParseTuple(args, "si", &name, &age))
وقمنا بإسناد هذه القيم ل name و age
ملحوظة: انت لن تقوم بالتعديل على name فافضل تعريف إنه يكون const فيكون تعريفه كالتالى
كود:
const char* name;
MyABS Function
كود:
static PyObject* myabs(PyObject* self, PyObject* args) { int number; if (!PyArg_ParseTuple(args, "i", &number)) return NULL; if (number<0){ number=-number; } return Py_BuildValue("i", number); }
اسندنا القيمة الى number (التحويل من Python Value إلى C Value)
ال Return لازم يكون عبارة عن Python Value (او PyObject) وهو ال Return Type الخاص بال myabs Function كما لاحظت، فبالتالى هنحتاج نحول من ال C Value إلى Python Value ودا هيتم عن طريق إستخدام Py_BuildValue
وهنا تم تحديد إن هيتم عمل return ل i وهو Integer وقيمته مساوية ل number
holaDict Function
كود:
static PyObject* holaDict(PyObject* self, PyObject* args) { const char* key; int value; if (!PyArg_ParseTuple(args, "si", &key, &value)) return NULL; return Py_BuildValue("{s:i}", key, value); //Returns a Dict. }
إذا حبيت تعمل return ب List فكل ماعليك هو إنك تعدل ال Format للتالى
return Py_BuildValue("[s,i]", key, value); //Returns a List object
وإذا حبيت تعمل return ب Tuple فكل ماعليك هو إنك تعد ال Format كالتالى (s,i)
hola Tuple Function
كود:
static PyObject* holaTuple(PyObject* self, PyObject* args) { const char* name; int age; if (!PyArg_ParseTuple(args, "si", &name, &age)) return NULL; return Py_BuildValue("(s,i)", name, age); //Returns a Tuple }
كود:
static PyObject* divTwo(PyObject* self, PyObject* args) { int first; int second; int result; if (!PyArg_ParseTuple(args, "ii", &first, &second)) return NULL; printf("First: %i\n", first); printf("Second: %i\n", second); if(second==0){ //DivByZeroError! PyErr_SetString(PyExc_ZeroDivisionError, "DivByZero"); return NULL; //Get out! } result = first/second; return Py_BuildValue("i", result); }
نوع ال Error هو PyExc_ZeroDivisionError وال message هتكون DivByZero
2- الضم لل Methods Table كالتالى
كود:
static PyMethodDef SimpleModuleMethods[]= { {"hola", hola, METH_VARARGS, "prints name and age"}, {"myabs", myabs, METH_VARARGS, "returns the abs of a number"}, {"divTwo", divTwo, METH_VARARGS, "DIV 2 "}, {"holaTuple", holaTuple, METH_VARARGS, "returns a tuple"}, {"holaDict", holaDict, METH_VARARGS, "returns a dict"}, {NULL, NULL, 0, NULL} };
3- عمل Initialize لل Module كالتالى بنستدعى فيها ال Py_InitModule Function كالتالى
كود:
PyMODINIT_FUNC initsimplemodule(void) { (void) Py_InitModule("simplemodule", SimpleModuleMethods); }
كود:
from distutils.core import setup, Extension modExt = Extension('simplemodule', sources = ['simplemodule.c']) setup (name = 'SimpleModPackage', version = '1.0', description = 'hola, myabs', ext_modules = [modExt])
5- عمل Build و Install كالتالى
كود:
python setup.py build python setup.py install
كود:
#!bin/python import simplemodule as sm sm.hola("Ahmed", 18) print sm.myabs(-10) #10 print sm.myabs(7) #7 print sm.holaDict("python", 1) print sm.holaTuple("ahmed", 999) print sm.divTwo(2, 0)
#Output:
Name: Ahmed
Age: 18
10
7
{'python': 1}
('ahmed', 999)
First: 2
Second: 0
Traceback (most recent call last):
File "C:\Python25\Projects\exten\smTest.py", line 10, in <module>
print sm.divTwo(2, 0)
ZeroDivisionError: DivByZero
Name: Ahmed
Age: 18
10
7
{'python': 1}
('ahmed', 999)
First: 2
Second: 0
Traceback (most recent call last):
File "C:\Python25\Projects\exten\smTest.py", line 10, in <module>
print sm.divTwo(2, 0)
ZeroDivisionError: DivByZero
References:
1- Extending Python
2- Programming Python 3rd Edition
Related:
1- Style Guide for C Code
2- SWIG
3- CXX
--Ahmed Youssef
تعليق