Author Topic: Extending Python with C | كتابة اضافات لبايثون بسى  (Read 773 times)

Ahmed Youssef

  • Helping Freak
  • Administrator
  • Active Member
  • *****
  • Posts: 242
    • View Profile
    • WWW
    • Email
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- اكتب التالى
Code: [Select]
#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);
}
نبدأ ب module بسيطة وهى hola هنشرح سطر سطر
اولا نعمل include ل python api header كالتالى
Code: [Select]
#include <Python.h>
اى Object فى Python تقدر تعرفه ك Pointer ل ب PyObject
احنا الأول عايزين نعرف function بسيطة تطبع كلمة Hola!
Code: [Select]
static PyObject* hola(PyObject* self, PyObject* args)
لاحظ self هو Pointer فى حال لو ال PyObject Class ودى هتكون ال method تبعه. لكن لو Function يبقة self هيكون NULL . عايزين يتم إستخدام ال Function كالتالى
Code: [Select]
>>> hola.hola()
Hola!
لاحظ شئ إن ال Function مش هتاخد اى argument و مش ليها return او ال Return ب NONE

فالأول نختبر هل فى arguments اتباصت لل Function او لأ
Code: [Select]
if (!PyArg_ParseTuple(args, "", NULL))
        return NULL;
PyArg_ParseTuple هى Function بتسخدم فى عمل Parse او تحليل لل Arguments وفيها بيتم تحويل ال Python Values ل C Values زى ماهنشوف فى مثال قادم.
args هى ال arguments اللى هتتباصى لل Function
هى بتعبر عن ال Data Type الخاص بال Argument مثلا s او i وهكذا
s: String
i: Integer
.. etc
NULL هنا بيعبر عن ال متغيرات اللى هتاخد القيم اللى اتباصت لل args -هنطلع عليها اكثر فى مثال قادم-
Code: [Select]
   return NULL; 
للخروج مباشرة من تنفيذ ال Function
بعد كدا نيجى لل ال Function هتعمله وهو طباعة كلمة Hola بإستخدام printf


printf("Hola!");
واخيرا زى ماقلنا اننا مش عايزين اى return من ال Function فهنعمل Py_RETURN_NONE
Code: [Select]
    Py_RETURN_NONE;
هنحتاج نعرف ال methodTable وهو عبارة عن array بتشمل معلومات عن ال Function زى الname, address وال Documentation الخاصة بيها وهكذا
Code: [Select]
static PyMethodDef HolaMethods[] =
{
     {"hola", hola, METH_VARARGS, "prints Hola"},
     {NULL, NULL, 0, NULL}
};
اول field هو ال name الخاص بال function
التانى هو ال function نفسها
التالت بيعبر عن ان ال Arguments اللى هتتباصى هى Python-Level Arguments وغالبا بنستخدم METH_VARAGS
الرابع هو الوصف الخاص بال function
ونحجز المكان التانى فى ال Array ب
Code: [Select]
{NULL, NULL, 0, NULL}
نعمل Initialize لل Module بتاعتنا كالتالى
Code: [Select]
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
Code: [Select]
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 كالتالى
Code: [Select]
modExt = Extension('hola', sources = ['hola.c'])
وفيه بنحدد ال source واسم الextension
    3- تجهيز ال setup script بإننا نسجل فيه إسم ال Package و الإصدار والوصف و ال extensions كالتالى

Code: [Select]
setup (name = 'HolaPackage',
        version = '1.0',
        description = 'Simple demo',
        ext_modules = [modExt])
كل ماعليك هو
Code: [Select]
Python setup.py build
وبعد كدا تعمل Install لل Package كالتالى
Code: [Select]
Python setup.py install
نجرب ال Hola Module كالتالى
    1- اعمل اى Test Script وليكن HolaTest.py
    2- اعمل import ل hola Module كالتالى
Code: [Select]
import hola 
    3- استدعى ال hola function كالتالى
Code: [Select]
hola.hola()
Quote
#output:
Hola!
    4- لنوضيح ال return الخاص بال Function اكتب
Code: [Select]
print hola.hola()
Quote
#Output:
Hola!
None
بعد ماطلعنا على الأساسيات نجرب نكتب module فيها function بتقبل argument ك name و age وتطبعهم و Function اخرى لحساب القيمة المطلقة لرقم وواحدة تقسم عددين وواحدة تعمل return ب Tuple, Dictionary
الفكرة بإختصار:
1- نعرف ال Functions
2- نضمهم لل Methods Table
3- نعمل Initialize لل Module
4- نجهز ال setup script
5- نعمل Build و Install لل Module
6- نستخدم ال Module عن طريق TestScript مثلا كالتالى


1- تعريف ال Functions

ال Hola Function
Code: [Select]
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;
}
Code: [Select]
 if (!PyArg_ParseTuple(args, "si", &name, &age))
لاحظ إننا هنا توقعنا إن هيتباصى لل Function التالىs وهى string و i وهى integer
وقمنا بإسناد هذه القيم ل name و age

ملحوظة: انت لن تقوم بالتعديل على name فافضل تعريف إنه يكون const فيكون تعريفه كالتالى
Code: [Select]
const char* name;
MyABS Function

Code: [Select]
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);

}
لاحظ اننا توقعنا إن هيتباصى لل Function التالى i وهو integer وبيعبر عن الرقم
اسندنا القيمة الى 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
Code: [Select]
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.
}
لاحظ ان بيتم إعادة Dictionary Object واحنا حددنا كدا بالجزئية دى{s:i}
إذا حبيت تعمل return ب List فكل ماعليك هو إنك تعدل ال Format للتالى
return Py_BuildValue("[s,i]", key, value); //Returns a List object
وإذا حبيت تعمل return ب Tuple فكل ماعليك هو إنك تعد ال Format كالتالى (s,i)


hola Tuple Function
Code: [Select]
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
}
divTwo Function
Code: [Select]
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);
}
إذا كان المقسوم عليه يساوى 0 يبقة فى Error! ونقدر نبلغ ال Interpreter بيه بإستخدامPyErr_SetString
نوع ال Error هو PyExc_ZeroDivisionError وال message هتكون DivByZero


2- الضم لل Methods Table كالتالى
Code: [Select]
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}
};
لاحظ إن آخر عنصر فى ال Array هو حاجز..

3- عمل Initialize لل Module كالتالى بنستدعى فيها ال Py_InitModule Function كالتالى

Code: [Select]
PyMODINIT_FUNC

initsimplemodule(void)
{
     (void) Py_InitModule("simplemodule", SimpleModuleMethods);
}
4- ال Setup Script كالتالى
Code: [Select]
from distutils.core import setup, Extension
 
modExt = Extension('simplemodule', sources = ['simplemodule.c'])
 
setup (name = 'SimpleModPackage',
        version = '1.0',
        description = 'hola, myabs',
        ext_modules = [modExt])
بنوضح فيه اسم ال Package والإصدار والوصف وال extension اللى بيشمل اسم ال module والsource
5- عمل Build و Install كالتالى
Code: [Select]
python setup.py build
python setup.py install
6- عمل Test Script واختبار ال Module كالتالى
Code: [Select]
#!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)
Quote
#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


References:

    1- Extending Python
    2- Programming Python 3rd Edition
   
Related:

    1- Style Guide for C Code
    2- SWIG
    3- CXX


« Last Edit: October 04, 2009, 04:43:56 PM by Ahmed Youssef »
Logged

Life is just a chance to grow a soul. - A. Powell
Weblog: http://ahmedyoussef.wordpress.com/