SWIG

از Pythoni.ca

پرش به: ناوبری, جستجو
این نوشته (SWIG) بخشی از کتاب عصر پایتون می باشد .
کتاب یک بایت از پایتون نکته ها و ترفند ها ماژول ها نمونه پروژه منابع آموزشینرم افزارهای جانبیکتابخانه‌های داخلی و خارجی پرسش و پاسخ


فهرست مندرجات

مقدمه

زبانهای اسکریپتی چون پرل ، پایتون و ... امروز بسیار مورد توجه قرار گرفته اند . چون توسعه نرم افزار ها توسط این زبانها بسیار آسان تر و سریعتر بوده و برنامه هایی پایدار تر و با اشکلات کمتری ایجاد می گردد . پس زبانهایی چون پایتون با فراهم آوردن امکانات بسیار و راحتی برنامه نویس و کاربر خواهان بسیاری داشته و خواهد داشت . اما شاید یکی از مشکلات این زبانهای عدم توانایی ذاتی و درونی آنها در انجام برخی کارها می باشد . برای مثال در انجام عملیات ریاضی یا گرافیکی که به سرعت پردازش بسیار نیازمند می باشد . یا هنگامی که قصد ارتباط و استفاده مستقیم از یک سخت افزار را داشته باشیم . در این گونه مواقع نیاز است تا از زبانهایی سطح پایین استفاده کنیم و پیاده سازی کامل این برنامه ها با پایتون یا سایر زبانهای اسکریپتی ممکن است باعث کاهش کارایی یا بکل غیر ممکن باشد !! البته لازم با اشاره هست که مفسر پایتون کمبودی در این زمینه ندارد ! و تمامی ماژول های مورد نیاز برای کار با انواع سخت افزار ها و نیز کار با توابع ریاضی یا توابعی که نیاز به سرعت پردازش دارند قبلا ایجاد شده است . پس اینگونه برداشت نگردد که مفسر پایتون ناقص بوده و مشکلی دارد ! در حیقیت این آموزش قصد دارد موارد خاصی را پوشش دهد که ما در برنامه خود نیاز به پیاده سازی بخشی از کد با یک زبان سطح پایین یا استفاده از کدهای نوشته شده داشته باشیم .

ابزار مورد استفاده در این بخش ابزار فوق العاده SWIG که مخفف شده عبارت Simplified Wrapper and Interface Generator می باشد . هدف از این ابزار امکان استفاده از قابلیت زبانهای C و ++C در زبانهای سطح بالایی چون PHP ، Python ، Perl و ... و حتی زبانهایی چون #C می باشد .

دریافت و نصب SWIG

SWIG ابزاری هست که توسط اقای Dave Beazley ایجاد و توسعه یافته هست . برای دانلود و نصب آن به سایت رسمی آن یعنی www.swig.org مراجعه کنید . در اکثر توزیع های معروف لینوکس این ابزار جزو بسته های رسمی می باشد . بنابراین می توانید خیلی راحت با ابزار هایی چون apt-get ، yum ، portage و ... این ابزار را نصب کنید . در سایت و نیز داخل بسته مستنداتی کافی برای نحوه نصب موجود می باشد . پس ما همین دلیل و نیز گسترده و متفاوت بودن توزیع ها و نحوه نصب از این بخش گذر می کنیم .

شروع با یک مثال ساده

فرض کنیم ما تابعی در C داریم که دو متغییر را دریافت و حاصل جمع آنها را برگشت می دهد . قصد ما امکان استفاده از این تابع نوشته شده با زبان C در داخل مفسر و برنامه نوشته شده با زبان پایتون هست . پس اولین مرحله ایجاد این برنامه در زبان C می باشد . بر خلاف سایر برنامه های نوشته شده در زبان C این برنامه نیازی به تابع main ندارد ! چون این کد یک مازول هست نه برنامه مستقل . کد مورد نظر بصورت زیر می باشد .


testModule.c تصویر:code.png
int add(int a, int b)
{
                return a+b;
}


این تابع را در داخل فایل testModule.c ذخیره کنید . اکنون باید یک فایل با پسوند i ایجاد کنید . ابزار SWIG با استفاده از توضیحات موجود در این فایل سایر فایل های مورد نیاز را تولید خواهد کرد . محتویات فایل test.i برای تابع مورد نظر بصورت زیر می باشد .


test.i تصویر:code.png
%module test
extern int add(int a, int b);


اکنون از طریق ابزار SWIG فایل های مورد نیاز را ایجاد می کنیم .



تصویر:terminal.png
$ swig-1.3 -python test.i


ممکن است بسته به نسخه SWIG نام فایل اجرایی آن متفاوت باشد . پس این دوسترات منحصر به فرد و کلی هست . با اجرای این فرمان بایستی دو فایل با نام های test.py و test_wrap.c ایجاد گردد . اکنون باید فایل های موجود را کامپایل کنیم . برای این کار به فایل های سرآیند (header) مفسر پایتون نیاز هست . مطمئن شوید این فایل ها نصب می باشد . معمولا این فایل ها باید در دایرکتوری چون /usr/include/python2.5/ موجود باشد . 2.5 نشان دهنده نسخه مفسر می باشد و ممکن هست متفاوت باشد . با این فرض دستور مناسب برای کامپایل باید این چنین باشد .



تصویر:terminal.png
$ gcc -fpic -c testModule.c test_wrap.c -I/usr/include/python2.5/


پارامتر fpic در برخی مواقع برای مثال سیستم های 64 بیتی نیاز می باشد . اگر نبود آن مشکلی ایجاد نمی کند می تواتید آنرا حذف کنید . اگر دستور بدون هیچ مشکلی و خطایی پایان یابد تک تک فایل های شما کامپایل شده هست . اما برای امکان استفاده از آن باید به هم لینک شوند . پس با فرمان زیر این کار را انجام می دهیم .



تصویر:terminal.png
$ gcc -shared testModule.o test_wrap.o -o _test.so


این دوستور نیز باید بدون مشکلی خاتمه یابد . حاصل کار فایل test.so_ خواهد بود . برای تست این ماژول از مفسر پایتون و در مسیر فایل مورد نظر استفاده می کنیم .


استفاده از ماژول تصویر:code.png
>>> import _test
>>> _test.add(2, 5)
7


دلیل ماژول نویسی

همانطور که قبلا گفته شد یکی از مهمترین دلایل استفاده بحث سرعت اجرا می باشد . زبانهای سطح پایین بیشتر به زبان ماشین نزدیک می باشد بینابراین سرعت اجرای بیشتری دارند . در عوض زبانهای بسیار سطح بالا چون پایتون هم بخاطر تفسیری بودن و هم بخاطر زیاد بودن امکانات و ویژگی ها سرعت کمتری دارند . چون اعمال این ویژگی ها باعث کاهش سرعت اجرا می گردد . در ادامه با طرح مثالی سرعت دو روش را بررسی می کنیم . هدف پیاده سازی تابع فاکتوریل با استفاده از دو روش موجود و مقایسه زمان لازم برای اجرای هر یک می باشد . ابتدا تابع فاکتوریل را با زبان سی پیاده سازی کرده و ماژول آن را همانند مراحل بالا ایجاد می کنیم . کد مورد نظر در زبان C بصورت زیر می باشد :


تابع فاکتوریل در زبان سی تصویر:code.png
int fact (int n)
{
                int f=1;
                while (n > 1){
                                f = f * n;
                                n = n - 1;
                }
                return f;
}


نام گذاری ماژول و فایل ها دقیقا همانند مثال بالا می باشد .

حال دقیقا با همین روش فایل دیگری با زبان پایتون ایجاد و نام آنرا fact.py قرار می دهیم .


تابع فاکتوریل در زبان پایتون تصویر:code.png
def fact(n):
	f = 1
	while n > 1:
		f = f * n
		n = n - 1
	return f


حال برای بررسی برنامه کوچک دیگری با نام compare.py می نویسیم .


compare.py تصویر:code.png
#!/usr/bin/env python
import fact, _test, time
pyfact = fact.fact
cfact = _test.fact

# cfact
start = time.time()
for i in range(1,2000000):
        cfact(12)
end = time.time()
print 'C factorial function used', end-start, 'seconds'

# pyfact
start = time.time()
for i in range(1,2000000):
        pyfact(12)
end = time.time()
print 'Python factorial function used', end-start, 'seconds'


خروجی برنامه compare.py باید شبیه به حالت زیر باشد :


نتایج مقایسه تصویر:code.png
bayazee@mbs:~/temp$ python compare.py
C factorial function used 1.44402885437 seconds
Python factorial function used 9.28429579735 seconds


مشخصات سیستمی که برنامه در آن تست شده است :


مشخصات سیستم تصویر:code.png
Linux 2.6.20-15-generic #2 SMP AMD Athlon(tm) 64 Processor 3000+


ممکن است با تغییر سخت افزار زمان اجرای هردو کد با نسبتی تقریبا یکسان کم یا زیاد شود . همانطور که از مثال و زمان مورد نیاز برای هر کد مشخص هست سرعت اجرای دو کد نوشته شده تفاوت بسیاری دارد . البته این امر دلیل بر ضعف پایتون نیست ! چون در این زمان پایتون دو میلیون بار فاکتوریل عدد 12 که برابر با 479001600 است را محاسبه کرده است ! پس همانطور که قبلا هم گفته شد این کمی سرعت فقط در مواقع و الگوریتم های بحرانی که دارای مرتبه پیچیدگی و زمانی زیادی باشند حس می گردد . و در سایر مواقع شاید متوجه کمی سرعت نشوید ! و حتی سرعت اجرای دو بارنامه شاید برابر باشد !! برای مثال اگر همین تست را با ده هزار بار تکرار انجام دهیم خروجی بصورت زیر خواهد بود .


نتایجی متفاوت تصویر:code.png
C factorial function used 0.0053551197052 seconds
Python factorial function used 0.0501019954681 seconds


همانطور که مشاهده می کنید اختلاف زمانی 5 صدم ثانیه هست !! پس خود پایتون به انداره کافی سریع می باشد و همیشه نیاز به ماژول نویسی برای آن نیستیم ! فقط در مواقع و الگوریتم های خاص !!

فایل های interface با پسوند i

در مثال های قبل از فایل هایی با پسوند i استفاده کردیم که ابزار SWIG با استفاده از این فایل ها فایل ها و کد های مورد نیاز را تولید می کرد . در حقیقت ابزار SWIG برای ایجاد کد های مورد نظر نیازی به اطلاع از نحوه کار درونی توابع ما ندارد ! و تنها اطلاع از رابط یا interface توابع کافی هست ! روابط یا پروتوتایپ ها تقریبا شامل همان تعریف کلی تابع می باشد که اطلاعاتی کلی در مورد مقادیر دریافتی یا پارامتر ها تعداد آنها ، نوع داده خروجی ، اسامی و ... را در بر دارد . با استفاده از این فایل SWIG فایل هایی موسوم به Wrappers را ایجاد می کند که همانند پوششی بر روی کد اصلی قرار می گیرند و به عنوان یک واسط وظیفه تبادل اطلاعات بین دو زبان را بر عهده دارند . برای مثال نوع متغییر ها را از یک زبان به زبان دیگر تبدیل می کنند .

دسترسی به متغییرهای تعریف شده در زبان C

متغییرهای سراسری تعریف شده در ماژول نوشته شده با C نیز در دسترس می باشد و می توان از داخل برنامه نوشته شده با پایتون به این متغییر ها دسترسی داشت . البته نحوه دسترسی به متغییر ها کمی متفاوت است . زمانی که شما در زبان پایتون یک متغییر تعریف می کنید برای مثال a = 3.4 نام a بک یک نقطه از حافظه اشاره می کند که مقدار 3.4 در آن ذخیره شده است . اگر سپس دستوری مانند b = a را اجرا کنید متغییر b نیز در حقیقت به همان نقطه از حافظه اشاره می کند . به بیانی دیگر مدیریت متغییر ها بصورت داینامیک و بر عهده مفسر می باشد . ولی در زبان سطح پایینی چون C یا ++C نحوه تعریف و مدیریت متغییر بسیار متفاوت می باشد . بنابراین روشی برای نگاشت مستقیم متغییر های بین دو زبان و محیط وجود ندارد ! برای در دسترس قرار دادن متغییر ها SWIG از یک شی خاص بنام cvar برای دسترسی به متغییر های تعریف شده در فایل interface استفاده می کند . یک مثال ساده را بیان می کنیم .

کد برنامه اینبار بصورت زیر است :


testModule.c تصویر:code.png
#include <stdio.h>

int year = 1386;
char * name = "Bayazee";

void PrintYear(void){
printf ("Year: %d\n", year);
}


فایل interface یا رابط این برنامه کمی متفاوت بوده و شامل متغییر ها نیز می باشد :


test.i تصویر:code.png
%module test
extern void PrintYear(void);

%inline %{
extern int year;
extern char * name;
%}


دقیقا همانند مثال های بالا ماژول را کامپایل و ایجاد می کنیم . پس از کامپایل ماژول متغییر های تعریف شده قابل دسترسی می باشد :


دسترسی به متغییرها تصویر:code.png
>>> import _test
>>> print dir(_test)
['PrintYear', '__doc__', '__file__', '__name__', 'cvar']
>>> _test.PrintYear()
Year: 1386
>>> _test.cvar
Swig global variables (name, year)

>>> _test.cvar.name
'Bayazee'
>>> _test.cvar.year = 2007
>>> _test.PrintYear()
Year: 2007
>>> t.cvar.year = "Python"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: in variable 'year' of type 'int'


نوشتن ماژول با استفاده از ++C

ایجاد ماژول با استفاده از زبان ++C دارای نکات ریزی می باشد که پس از چند بار تمرین ملموس تر خواهد بود . چون خود مفسر پایتون با زبان C نوشته شده است توسعه آن با زبان C آسان تر می باشد . در ادامه با مثالی آسان یک ماژول با استفاده از ++C برای پایتون می نویسیم .

در اولین گام یک فایل سرآیند برای برنامه خود ایجاد می کنیم که شامل تعریف کلاس می باشد . فایل را با نام test.h ذخیره می کنیم .


test.h تصویر:code.png
class CRectangle {
    int x, y;
  public:
    void set_values (int,int);
    int area (void);
};


اکنون کد برنامه خود را که شامل توابع کلاس مورد نظر می باشد در دخل فایل test.cpp ذخیره می کنیم .


test.cpp تصویر:code.png
#include "test.h"
#include <iostream>
using namespace std;


void CRectangle::set_values (int a, int b) {
  x = a;
  y = b;
}

int CRectangle::area(void) {
  return (x*y);
}


مرحله بعدی ایجاد فایل رابط یا interface می باشد که با نام test.i ذخیره خواهد شد .


test.i تصویر:code.png
%module test

%{
#include "test.h"
%}

class CRectangle{
	int x, y;
public:
	 void set_values (int, int);
	 int area(void);
};


اکنون با استفاده از SWIG فایل های مورد نیاز را ایجاد می کنیم .



تصویر:terminal.png
$ swig-1.3 -python -c++ test.i 


مرحله بعدی کامپایل فایل های سورس می باشد .



تصویر:terminal.png
$ g++ -fpic -I/usr/include/python2.5 -c test.cpp test_wrap.cxx


اگر خطایی در روند کامپایل وجود داشت بایستی خطا را تصحیح و دوباره امتحان کنید . این خطا ممکن است در فایل کد های نوشته شده برنامه باشد یا اینکه فایل interface درست نبوده و باعث ایجاد فایل های اشتباه توسط SWIG گردد .

مرحله بعدی لینک فایل ها و ایجاد مازول نهایی است :



تصویر:terminal.png
gcc -shared -o _test.so test.o test_wrap.o /usr/lib/libstdc++.so.6.0.8


ماژول ایجاد شده را توسط مفسر پایتون اجرا شده در همان مسیر وارد می کنیم و محتویات آنرا چاپ می کنیم .


ماژول سی پلاس پلاس تصویر:code.png
>>> import _test
>>> test = _test
>>> print dir(test)
['CRectangle_area', 'CRectangle_set_values', 'CRectangle_swigregister', '__doc__', '__file__', '__name__',
 'delete_CRectangle', 'new_CRectangle']


همانطور که مشاهده می کنید نام توابع تغییر کرده و چند تابع جدید نیز موجود هست . برای ایجاد یک موجودیت جدید از این کلاس از تابع new_CRectangle استفاده می گردد . در صورتی که کلاس نوشته شده در ++C دارای تابع سازنده باشد ، تابع سازنده در این مرحله اجرا می گردد . تابع delete_CRectangle برای حذف متغییرها بکار می رود . توابع set_values و area نیز به ترتیب به توابع CRectangle_set_values و CRectangle_area نگاشت شده اند . اکنون از این کلاس استفاده می کنیم . ابتدا یک مستطیل ایجاد کرده و سپس محیط آنرا محاسبه می کنیم .


استفاده از ماژول سی پلاس پلاس تصویر:code.png
>>> r = test.new_CRectangle()
>>> test.CRectangle_set_values(r, 4, 6)
>>> test.CRectangle_area(r)
24


همانطور که ملاحظه می کنید روند استفاده از کلاس موجود در ماژول ، معمولی و همانند کلاسهای ذاتی پایتون نمی باشد . برای تشخیص نوع کلاس نام کلاس یعنی CRectangle به اول هر تابع اضافه شده است . و نیز برای اجرای هر تابع باید آدرس شی را که قصد داریم تابع مربوط به آن شی (در اینجا r) بر روی آن انجام گیرد به عنوان اولین پارامتر به آن ارسال کنیم .

ایجاد کلاس Wrapper

در بخش قبلی دیدیم که کلاس های موجود در ماژول های نوشته شده با ++C دارای رابطی نامناسب و غیر معمول می باشند که استفاده از آنها مشکل می باشد . می توان کلاس های Wrapper ی توسط پایتون نوشت که همانند یک پوشش بر روی کلاس اصلی قرار گرفته و استفاده از آن را تسهیل کرده و امکاناتی را فراهم می کنند . کلاس Wrapper مثال قبل بصورت زیر می باشد .


wtest.py تصویر:code.png
from _test import *
class Rectangle:
	def __init__(self):
		self.this = new_CRectangle()
		return 
	def set_value(self, a, b):
		CRectangle_set_values(self.this, a, b)
		return 
	def area(self):
		return Crectangle_area(self.this)


اکنون از طریق کلاس Wrapper موجود که با نام wtest.py ذخیره شده است دوباره و این بار بصورت غیر مستقیم و بسیار راحتتر و معمول تر از این ماژول استفاده می کنیم .


استفاده از ماژول Wrapper شده سی پلاس پلاس تصویر:code.png
>>> from wtest import Rectangle
>>> r = Rectangle()
>>> r.set_value(4, 6)
>>> print r.area()
24