توسعه پایتون با C — راهنمای کاربردی
در این مطلب، چگونگی توسعه پایتون با C همراه با ارائه مثال و کدهای کامل آنها مورد بررسی قرار گرفته است.
توسعه پایتون با C
بسیاری از توسعهدهندگان هنگامی که کد خود را نوشتند و شروع به اجرای آن کردند، با مشکلاتی در حوزه کارایی مواجه میشوند. در اینجا است که باید برای غلبه بر چالشهای موجود، کد خود را «بهینه» (Optimize) کنند. در این وهله، دستهای از گزینهها در مقابل فرد قرار دارد که در ادامه، مورد بررسی قرار میگیرند.
- پایین آوردن «پیچیدگی زمانی» (Time Complexity) یک گزینه مهم برای افزایش کارایی است. در این راستا، باید از الگوریتمهای سریعتر استفاده کرد. به طور ایدهآل، این همان کاری است که فرد باید از ابتدا انجام دهد؛ انتخاب یک الگوریتم با کارایی بهتر و بالاتر و در واقع، پیچیدگی زمانی و «فضایی» (Space Complexity) کمتر. اما گاهی از مواقع، امکان استفاده از الگوریتم دیگر و یا الگوریتمی با کارایی بالاتر وجود ندارد. در این شرایط است که فرد باید به سراغ گزینههای ۲ یا ۳ برود.
- موازیسازی کد گزینه دیگری است که برای افزایش کارایی کد مطرح میشود. اگر چندین پردازشگر وجود داشته باشد، میتوان وظایف پردازش را در زیر وظایفی تقسیم کرد تا به صورت همزمان پردازش شوند. اما این نیز کار دشواری است. تنها وظایف مشخصی قابل موازیسازی هستند و حتی اگر یک وظیفه قابل موازیسازی باشد، تضمین نمیکند که نسبت به همتای غیر موازی خود سریعتر باشد.
- یک راهکار دیگر، انتقال دادن قسمتهای کندتر کد به یک زبان سریعتر است. این کار برای افرادی که بیش از یک زبان برنامهنویسی را بلد هستند، کار سادهتری است. این راهکار، محل تمرکز این مطلب است.
در ادامه این مطلب، راهکار سوم با تمرکز بر توسعه پایتون با C مورد بررسی قرار گرفته است.
محاسبه اعداد اول
در اینجا، یک مشکل نسبتا ساده برای حل کردن بیان شده و در واقع کدی ارائه شده است تا کارایی آن با استفاده از توسعه پایتون با C افزایش پیدا کند. هدف این کد، محاسبه kاُمین عدد اول است. بنابراین، در ادامه یک راهکار برای حل این مساله در «زبان برنامهنویسی پایتون» (Python Programming Language) ارائه شده است.
باید کدی که در ادامه ارائه میشود را با استفاده از روش توسعه پایتون با C بهینه کرد.
1def isPrime(n):
2 for num in range(2, n//2):
3 if n%num == 0:
4 return False
5 return True
6
7def kth_prime(k):
8 candidate = 2
9 while k:
10 if isPrime(candidate):
11 k -= 1
12 candidate += 1
13 return candidate - 1
14
15# Driver code
16print(kth_prime(10000)) # The 10000th prime number is 104723
17
18"""
1944.34user 0.00system 0:44.57elapsed 99%CPU (0avgtext+0avgdata 8572maxresident)k
200inputs+0outputs (0major+1587minor)pagefaults 0swaps
21"""
اجرای کدی که در اینجا وجود دارد، برای محاسبه ۱۰۰۰۰اُمین عدد اول، ۴۲ ثانیه به طول میانجامد. در ادامه، کد C برای محاسبه ۱۰۰۰۰اُمین عدد اول ارائه شده است. در کد C از الگوریتمی مشابه با آنچه برای نوشتن کد پایتون مورد استفاده قرار گرفت، استفاده شده است.
1#include<stdio.h>
2#include "primeheader.h"
3
4int isPrime(int n) {
5 for (int i=2; i<n/2; i++) {
6 if (n%i == 0)
7 return 0;
8 }
9 return 1;
10}
11
12int kthPrime(int k) {
13 int candidate = 2;
14 while (k) {
15 if (isPrime(candidate))
16 k--;
17 candidate++;
18 }
19 return candidate-1;
20}
21
22// Driver code
23int main() {
24 printf("%d\n", kthPrime(10000));
25 return 0;
26}
1extern int kthPrime(int n);
اجرای این کد، بر خلاف کد پایتون، تنها ۱/۰۱ ثانیه زمان میبرد. با وجود محاسبات انجام شده و خروجیهایی که از آنها به دست آمده است، حدس زدن این موضوع سخت نیست که اگر میشد پایتون از این ماژول برای محاسبه kاُمین عدد اول استفاده کند (به جای کد نوشته شده به خود زبان پایتون)، صرفهجویی زیادی در زمان اتفاق میافتاد. خوشبختانه، امکان این کار وجود دارد.
پایتون، دستهای از «رابطهای برنامهنویسی کاربردی» (Application Programming Interface | API) را فراهم میکند که با استفاده از آنها میتوان کارکردهای پایتون را افزایش داد. در ادامه، یک کتابخانه پایتون ساده با استفاده از زبان برنامهنویسی C ساخته میشود که امکان محاسبه kاُمین عدد اول را دارد. روش کلی انجام پردازش در ادامه بیان شده است.
- ابتدا باید کد مربوط به کارکرد مورد نظر را در C نوشت. در اینجا، این کار پیش از این انجام شده است.
- کارکردهایی که با C ساخته میشود به منظور کار با پایتون باید یکپارچهسازی شود.
- در نهایت، همه چیز باید ساخته شود.
یکپارچهسازی
این کارکردها برای پیادهسازی CPython از پایتون ساخته میشوند. همه چیز در CPython ساختاری به نام PyObject است. توابع، کلاسها، انواع دادهها و دیگر موارد، توسط کاربر نامگذاری میشوند. این یک PyObject است، بنابراین، یکی از کارهایی که باید در این مرحله انجام شود، تبدیل بین انواع داده C، توابع و دیگر موارد به چیزهایی است که پایتون میتواند از آنها استفاده کند.
در مثال ارائه شده در این مطلب با هدف آموزش توسعه پایتون با C همه چیز به PyObjects تبدیل میشود.
1#include "python3.6m/Python.h" // Python provides its API via Python.h header file
2#include "primeheader.h"
3
4// A static function which takes PyObjects arguments and returns a PyObject result
5static PyObject* py_kthPrime(PyObject* self, PyObject* args) {
6 int n;
7 if (!PyArg_ParseTuple(args, "i", &n)) // Validate and parse the arguments received to function so that its usable by C
8 return NULL;
9 return Py_BuildValue("i", kthPrime(n)); // Get result from C, wrap it up with a PyObject and return it
10}
11
12// Define a collection of methods callable from our module
13static PyMethodDef PyFastPrimeMethods[] = {
14 {"kthPrime", py_kthPrime, METH_VARARGS, "Finds the kth prime number"}
15};
16
17// Module definition
18static struct PyModuleDef fastprimemodule = {
19 PyModuleDef_HEAD_INIT,
20 "fastprime",
21 "This module calculates the kth prime number",
22 -1,
23 PyFastPrimeMethods
24};
25
26// This method is called when you import your code in python. It instantiates the module and returns a reference to it
27PyMODINIT_FUNC PyInit_fastprime(void)
28{
29 return PyModule_Create(&fastprimemodule);
30}
در صورتی که برنامهنویسان با زبانهای با نوع قوی مانند «سیپلاسپلاس» (++C) یا «جاوا» (Java) آشنایی نداشته باشند، کد بالا کمی وحشتناک به نظر میرسد؛ اما حقیقت این است که این کار واقعا آسان است. همانطور که پیش از این اشاره شد، همه چیز یک PyObject است. در خط ۵، یک تابع ایستا تعریف شد که یک اشارهگر را به PyObject باز میگرداند. این تابع، همان تابعی است که توسط CPython هنگام «وارد» (Import) کردن کتابخانه و فراخوانی kthprime، فراخوانی میشود.
این تابع، دو آرگومان self و args را دریافت میکند. آرگومان self به PyObject اشاره دارد که تابع را فراخوانی کرده و یا تابع یا نمونه ماژولی را خودش فراخوانی کرده است. با توجه به اینکه آرگومان k به عنوان آرگومان موقعیتی پاس داده شده است، پایتون به صورت داخلی آن را به عنوان «تاپلی» (Tuple) با یک عنصر پاس میدهد. «i» به PyArg_ParseTuple میگوید که فرد به دنبال یک عدد صحیح است. اگر در هر نقطهای در متد استاتیک یک اشارهگر NULL بازگردانده شود، مفسر پایتون فرض میکند که چیزی اشتباه پیش رفته است و یک استثنا را اجرا میکند. اگر تابع هیچ چیزی را باز نگرداند، شی Py_None پاس داده میشود.
ساخت
اکنون، زمان آن رسیده است تا همه چیز در کنار هم قرار بگیرد. ابتدا، کد c_prime.c به یک فرمت قابل جا به جایی کامپایل میشود. GCC به فرد این امکان را میدهد تا کد C را به یک فایل شی (Object File) کامپایل کند. این فایل شی حاوی کدی در فایل c_prime.c در فرمت کد ماشین است.
سپس، هدف کامپایل کردن کد fastprime.c به کد شیئی است که پیش از این آماده شد و در نهایت، همه فایلهای شی با کتابخانه استاندارد به یکدیگر لینک داده میشوند تا کد قابل استفاده نهایی ساخته شود. این کار میتواند فعالیتی بسیار خسته کننده باشد و کاربر معمولا تمایل دارد که در صورت امکان، از انجام چند باره آن اجتناب کند. بنابراین، پایتون یک کتابخانه disutils فراهم میکند تا همه این کارها را کمی سادهتر کند.
1from distutils.core import setup, Extension
2
3setup(name='fastprime',
4 ext_modules=[
5 Extension('fastprime',
6 ['fastprime.c'],
7 extra_objects=["c_prime.o"] # Relocatable compiled code of the c_prime to prevent recompiling each time
8 )
9 ]
10)
11
12# python setup.py build
با استفاده از کتابخانه مذکور، در نهایت ماژول پایتون با پسوند so. (شی به اشتراکگذاری شده یا همان Shared Object؛ مشابه با فایلهای dll. در ویندوز) ساخته میشود. این شی به اشتراکگذاری شده را میتوان وارد پایتون کرد و از آن برای محاسبه kاُمین عدد اول، استفاده کرد.
1import fastprime
2print(fastprime.kthPrime(10000))
اجرای این قطعه کد، ۱/۰۱ ثانیه زمان میبرد. بنابراین، با استفاده از راهکار بیان شده، برنامه ۴۰ برابر سریعتر شده است. اما اکنون این پرسش مطرح میشود که اگر این راهکار اینچنین سودمند است، چرا افراد، بیشتر از آن استفاده نمیکنند؟ نکته اول این است که انجام این کار برای هر تابع کوچکی که ممکن است در پایتون مورد استفاده قرار بگیرد، بسیار زمانبر است. کارایی این روش بسیار وابسته به سیستمی است که مورد استفاده قرار میگیرد و بنابراین، نباید برای هر شرایطی از این راهکار به عنوان گزینه اولیه استفاده کرد.
ایده نهفته در پس پایتون آن است که کاربر زمان بیشتری را به خواندن کد تخصیص دهد، نه نوشتن آن. بنابراین، قابل خواندنتر کردن کد این امکان را فراهم میکند تا کد برای مدت طولانیتری مناسب باقی بماند. از پایتون انتظار نمیرود که در بحث سرعت از C یا ++C سبقت بگیرد. نکته دوم این است که این افزایش قیمت، دارای هزینه است. کد پایتون اولیه نوشته شده، مستقل از ماشین است. پایتون کد را به بایتکدها کامپایل میکند و سپس، این بایت کدها در «ماشین مجازی پایتون» (Python Virtual Machine) تفسیر میشوند.
بنابراین، تنها میتوان به پیش رفت و کد را توزیع کرد و اجازه داد تا پایتون همه موارد دیگر را مدیریت کند (تا هنگامی که از نسخه سازگار پایتون برای اجرای کد استفاده میشود). از سوی دیگر، مشکل C یا ++C این نیست. فایلهای شی تولید شده به وسیله C و ++C وابسته به ماشین هدف هستند. بنابراین، با استفاده از فایلهای شی C، کدهای پایتون به طور موثری وابسته به ماشین شدهاند.
سلام یک کتابخونه در c دارم می خواهم در پایتون از توابع اون کتابخونه استفاده کنم. این روش به نظرم مناسب است. اما به نظرم اگر بهتر و واضحتر منظورم ذکر مثال با عکس بود. افراد مبتدی مثل من هم می توانستند استفاده کنند. افراد حرفه ای که بلدند، این مقاله را اگر واضح تر و با ریز و جزییات بیشتر بنویسید ممنون می شوم.