از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
تقویت پایتون با برنامه های افزودنی C سفارشی
سرفصلهای مطلب
معرفی
این مقاله قصد دارد ویژگیهای C API CPython را که برای ساخت پسوندهای C برای پایتون استفاده میشود، برجسته کند. من روند کار کلی را برای گرفتن یک کتابخانه کوچک از نمونه اسباب بازی های نسبتاً پیش پا افتاده، توابع C و قرار دادن آن در معرض پوشش پایتون بررسی خواهم کرد.
شاید تعجب کنید… پایتون یک زبان سطح بالا و فوق العاده است که تقریباً قادر به انجام هر کاری است، چرا من بخواهم با کدهای C درهم و برهم کار کنم؟ و من باید با فرض کلی آن استدلال موافق باشم. با این حال، دو مورد استفاده معمولی وجود دارد که من پیدا کردهام که احتمالاً این مورد به وجود میآید: (i) برای افزایش سرعت بخشی خاص از کد پایتون و (۲) شما مجبور هستید برنامهای را که قبلاً در C نوشته شده است را در یک برنامه قرار دهید. برنامه پایتون را ایجاد کنید و نمی خواهید کد C را در پایتون بازنویسی کنید. اخیراً برای من اتفاق افتاد و می خواستم آنچه را که یاد گرفته ام با شما به اشتراک بگذارم.
خلاصه مراحل کلیدی
- کد C را دریافت یا بنویسید
- تابع Wrapper API Python C را بنویسید
- جدول تابع(ها) را تعریف کنید
- ماژول را تعریف کنید
- تابع مقداردهی اولیه را بنویسید
- بسته بندی و ساخت افزونه
به دست آوردن یا نوشتن کد C
برای این آموزش من با مجموعه کوچکی از توابع C کار خواهم کرد که با دانش محدود خود از C نوشتم. همه برنامه نویسان C که این را می خوانند لطفاً ترحم کنند. روی من برای کدی که می خواهید ببینید.
unsigned long cfactorial_sum(char num_chars());
unsigned long ifactorial_sum(long nums(), int size);
unsigned long factorial(long n);
#include <stdio.h>
#include "demolib.h"
unsigned long cfactorial_sum(char num_chars()) {
unsigned long fact_num;
unsigned long sum = 0;
for (int i = 0; num_chars(i); i++) {
int ith_num = num_chars(i) - '0';
fact_num = factorial(ith_num);
sum = sum + fact_num;
}
return sum;
}
unsigned long ifactorial_sum(long nums(), int size) {
unsigned long fact_num;
unsigned long sum = 0;
for (int i = 0; i < size; i++) {
fact_num = factorial(nums(i));
sum += fact_num;
}
return sum;
}
unsigned long factorial(long n) {
if (n == 0)
return 1;
return (unsigned)n * factorial(n-1);
}
فایل اول demolib.h
یک فایل هدر C است که امضاهای تابعی را که با آنها کار خواهم کرد و فایل دوم را مشخص می کند demolib.c
اجرای واقعی آن توابع را نشان می دهد.
اولین تابع cfactorial_sum(char num_chars())
یک رشته C از ارقام عددی را دریافت می کند که با آرایه ای از کاراکترها نشان داده شده است که در آن هر کاراکتر یک عدد است. تابع با حلقه زدن بر روی هر char، تبدیل آن به int، محاسبه فاکتوریل آن int از طریق یک جمع می سازد. factorial(long n)
و آن را به جمع تجمعی اضافه می کنیم. در نهایت مجموع را به کد مشتری که آن را فراخوانی می کند برمی گرداند.
تابع دوم ifactorial_sum(long nums(), int size)
مشابه رفتار می کند sfactorial_sum(...)
، اما بدون نیاز به تبدیل به ints.
آخرین تابع ساده است factorial(long n)
تابع پیاده سازی شده در یک الگوریتم نوع بازگشتی.
نوشتن توابع Wrapper API Python C
نوشتن تابع Wrapper C به Python درگیرترین بخش کل است process من قصد دارم نشان دهم. API پسوند Python C که من از آن استفاده خواهم کرد در فایل هدر C Python.h است که با اکثر نصب های CPython همراه است. برای هدف این آموزش من از توزیع آناکوندا CPython 3.6 استفاده خواهم کرد.
اول از همه، من فایل هدر Python.h را در بالای یک فایل جدید به نام قرار می دهم demomodule.c
، و فایل هدر سفارشی خود را نیز اضافه خواهم کرد demolib.h
زیرا به نوعی به عنوان رابطی برای توابعی که قرار است بسته بندی کنم عمل می کند. این را هم اضافه کنم که تمام فایل هایی که با آنها کار می کنیم باید در یک دایرکتوری باشند.
#include <Python.h>
#include "demolib.h"
حالا من شروع به کار خواهم کرد روی تعریف wrapper به اولین تابع C cfactorial_sum(...)
. تابع باید ثابت باشد زیرا دامنه آن باید فقط به این فایل محدود شود و باید a را برگرداند PyObject
از طریق فایل هدر Python.h در معرض برنامه ما قرار می گیرد. نام تابع wrapper خواهد بود DemoLib_cFactorialSum
و شامل دو آرگومان خواهد بود، هر دو نوع PyObject
با اولی اشاره گر به خود و دومی اشاره گر به args که از طریق کد پایتون فراخوانی به تابع ارسال می شود.
static PyObject *DemoLib_cFactorialSum(PyObject *self, PyObject *args) {
...
}
در مرحله بعد باید رشته اعدادی را که کد پایتون کلاینت به این تابع ارسال می کند تجزیه کنم و آن را به یک آرایه کاراکتر C تبدیل کنم تا بتوان از آن توسط cfactorial_sum(...)
تابع برای برگرداندن مجموع فاکتوریل. من این کار را با استفاده از PyArg_ParseTuple(...)
.
ابتدا باید یک اشاره گر C char به نام تعریف کنم char_nums
که محتویات رشته پایتون در حال ارسال به تابع را دریافت می کند. بعد زنگ میزنم PyArg_ParseTuple(...)
عبور از آن PyObject
مقدار args، یک رشته قالب "s"
که مشخص می کند که اولین (و تنها) پارامتر args یک رشته است که باید در آخرین آرگومان، char_nums
متغیر.
اگر خطایی رخ دهد در PyArg_ParseTuple(...)
این استثنا خطای نوع مناسب را افزایش می دهد و مقدار بازگشتی صفر خواهد بود که در یک شرطی به عنوان false تعبیر می شود. اگر خطایی در بیانیه if من شناسایی شود، a را برمیگردانم NULL
، که به کد پایتون فراخوانی سیگنال می دهد که یک استثنا رخ داده است.
static PyObject *DemoLib_cFactorialSum(PyObject *self, PyObject *args) {
char *char_nums;
if (!PyArg_ParseTuple(args, "s", &char_nums)) {
return NULL:
}
}
من می خواهم کمی وقت بگذارم تا در مورد چگونگی این صحبت کنم PyArg_ParseTuple(...)
تابع کار می کند من یک مدل ذهنی در اطراف تابع ساخته ام به طوری که آن را به عنوان گرفتن تعداد متغیر آرگومان های موقعیتی می بینم که به تابع پایتون کلاینت ارسال شده و توسط تابع گرفته شده است. PyObject *args
پارامتر. سپس به استدلالهایی فکر میکنم *args
پارامتری که در متغیرهای C تعریف شده که بعد از مشخص کننده رشته فرمت قرار می گیرند، باز می شود.
جدول زیر نشان می دهد که من احساس می کنم مشخص کننده های فرمت رایج تر هستند.
مشخص کننده | نوع C | شرح |
---|---|---|
ج | کاراکتر | رشته پایتون به طول 1 به char C تبدیل شد |
س | آرایه char | رشته پایتون به آرایه char C تبدیل شد |
د | دو برابر | شناور پایتون به یک دوبل C تبدیل شد |
f | شناور | شناور پایتون به شناور C تبدیل شد |
من | بین المللی | Python int به C int تبدیل شد |
ل | طولانی | Python int به طول C تبدیل شد |
o | PyObject * | شی پایتون به یک PyObject C تبدیل شد |
اگر چندین آرگومان را به تابعی ارسال میکنید که باید بستهبندی شود و به انواع C منتقل شود، آنگاه به سادگی از چندین مشخصکننده مانند PyArg_ParseTuple(args, "si", &charVar, &intVar)
.
خوب، حالا که ما یک احساسی برای چگونگی آن داریم PyArg_ParseTuple(...)
آثار من حرکت خواهد کرد. کار بعدی این است که با آن تماس بگیرید cfactorial_sum(...)
تابع عبور از آن char_nums
آرایه ای که به تازگی از رشته پایتون که به wrapper ارسال شده است ساخته ایم. بازگشت طولانی بدون امضا خواهد بود.
static PyObject *DemoLib_cFactorialSum(PyObject *self, PyObject *args) {
unsigned long fact_sum;
fact_sum = cfactorial_sum(char_nums);
}
آخرین کاری که باید در DemoLib_cFactorialSum(...)
تابع wrapper این است که مجموع را به شکلی برگرداند که کد پایتون مشتری بتواند با آن کار کند. برای این کار از ابزار دیگری به نام استفاده می کنم Py_BuildValue(...)
از طریق گنجینه Python.h در معرض دید قرار گرفت. Py_BuildValue
از مشخص کننده های فرمت بسیار شبیه به روش استفاده می کند PyArg_ParseTuple(...)
آنها را درست در جهت مخالف استفاده می کند. Py_BuildValue
همچنین امکان بازگرداندن ساختارهای داده آشنای پایتون مانند تاپل ها و دیکت ها را فراهم می کند. در این تابع wrapper من یک int را به پایتون برمی گردانم که به صورت زیر پیاده سازی می کنم:
static PyObject *DemoLib_cFactorialSum(PyObject *self, PyObject *args) {
return Py_BuildValue("i", fact_sum);
}
در اینجا چند نمونه از برخی دیگر از قالبها و انواع ارزش بازگشتی آورده شده است:
کد لفاف | به پایتون بازگشت |
---|---|
Py_BuildValue(“s”، “A”) | “آ” |
Py_BuildValue(“i”، 10) | 10 |
Py_BuildValue(“(iii)”، 1، 2، 3) | (1، 2، 3) |
Py_BuildValue(“{si,si}”، “a’، 4، “b”، 9) | {“a”: 4، “b”: 9} |
Py_BuildValue(“”) | هیچ یک |
باحال، درسته!؟
حالا بیایید بگیریم روی برای پیاده سازی wrapper به تابع C دیگر ifactorial_sum(...)
. این لفاف قرار است شامل چند ویژگی عجیب و غریب دیگر برای کار کردن باشد.
static PyObject *DemoLib_iFactorialSum(PyObject *self, PyObject *args) {
PyObject *lst;
if(!PyArg_ParseTuple(args, "O", &lst)) {
return NULL;
}
}
همانطور که می بینید، امضای تابع مانند آخرین مثال است که ثابت است، a را برمی گرداند PyObject
، و پارامترها دو هستند PyObjects
. با این حال، تجزیه استدلال کمی متفاوت است. از آنجایی که تابع Python به لیستی منتقل می شود که نوع C قابل تشخیصی ندارد، باید از ابزارهای بیشتری از Python C API استفاده کنم. مشخص کننده فرمت “O” در PyArg_ParseTuple
نشان می دهد که الف PyObject
انتظار می رود، که به ژنریک اختصاص داده می شود PyObject *lst
متغیر.
در پشت صحنه، ماشین API Python C تشخیص میدهد که آرگومان ارسال شده، رابط توالی را پیادهسازی میکند، که به من اجازه میدهد با استفاده از PyObject_Length
تابع. اگر به این تابع a داده شود PyObject
نوعی که رابط توالی را پیاده سازی نمی کند و سپس a NULL
برگردانده می شود.
int n = PyObject_Length(lst);
if (n < 0) {
return NULL;
}
اکنون که اندازه لیست را میدانم، میتوانم عناصر آن را به یک آرایه C از ints تبدیل کرده و آن را به من وارد کنم ifactorial_sum
تابع C که قبلا تعریف شده بود. برای انجام این کار، من از یک حلقه برای تکرار بر روی عناصر لیست استفاده می کنم و هر مورد را با استفاده از آن بازیابی می کنم. PyList_GetItem
، که a را برمی گرداند PyObject
پیاده سازی شده به عنوان یک نمایش پایتون از یک نام طولانی PyLongObject
. بعد استفاده میکنم PyLong_AsLong
برای تبدیل نمایش پایتون یک long به نوع داده معمول C طولانی و پر کردن آرایه C از longs که نام بردم nums
.
long nums(n);
for (int i = 0; i < n; i++) {
PyLongObject *item = PyList_GetItem(lst, i);
long num = PyLong_AsLong(item);
nums(i) = num;
}
در این مرحله می توانم با من تماس بگیرم ifactorial_sum(...)
عملکرد عبور از آن nums
و n
، که مجموع فاکتوریل آرایه طولانی را برمی گرداند. باز هم استفاده خواهم کرد Py_BuildValue
برای تبدیل مجموع به یک int پایتون و برگرداندن آن به کد پایتون کلاینت فراخوان دهنده.
unsigned long fact_sum;
fact_sum = ifactorial_sum(nums, n);
return Py_BuildValue("i", fact_sum);
باقیمانده کدی که باید نوشته شود به سادگی کد API Python C است که زمان کمتری برای توضیح آن صرف می کنم و خواننده را به آن ارجاع می دهم. اسناد برای جزئیات
جدول تابع(ها) را تعریف کنید
در این بخش آرایه ای را می نویسم که دو تابع wrapper نوشته شده در بخش قبل را به نامی که در پایتون در معرض نمایش قرار می گیرد مرتبط می کند. این آرایه همچنین نشان دهنده نوع آرگومان هایی است که به توابع ما ارسال می شوند. METH_VARARGS
، و یک رشته سند در سطح تابع را ارائه می دهد.
static PyMethodDef DemoLib_FunctionsTable() = {
{
"sfactorial_sum",
DemoLib_cFactorialSum,
METH_VARARGS,
"Calculates factorial sum from digits in string of numbers"
}, {
"ifactorial_sum",
DemoLib_iFactorialSum,
METH_VARARGS,
"Calculates factorial sum from list of ints"
}, {
NULL, NULL, 0, NULL
}
};
ماژول را تعریف کنید
در اینجا من یک تعریف ماژول ارائه خواهم کرد که با تعریف قبلی مرتبط است DemoLib_FunctionsTable
آرایه به ماژول این ساختار همچنین مسئول تعریف نام ماژولی است که در پایتون در معرض نمایش قرار می گیرد و همچنین یک رشته سند در سطح ماژول را ارائه می دهد.
static struct PyModuleDef DemoLib_Module = {
PyModuleDef_HEAD_INIT,
"demo",
"Demo Python wrapper for custom C extension library.",
-1,
DemoLib_FunctionsTable
};
تابع Initialization را بنویسید
آخرین بیت کد C-ish برای نوشتن تابع مقداردهی اولیه ماژول است که تنها عضو غیر ایستا کد wrapper است. این تابع یک قرارداد نامگذاری بسیار خاص دارد PyInit_name
جایی که name
نام ماژول است. این تابع در مفسر پایتون فراخوانی می شود که ماژول را ایجاد می کند و آن را قابل دسترسی می کند.
PyMODINIT_FUNC PyInit_demo(void) {
return PyModule_Create(&DemoLib_Module);
}
کد برنامه افزودنی کامل اکنون به شکل زیر است:
#include <stdio.h>
#include <Python.h>
#include "demolib.h"
static PyObject *DemoLib_cFactorialSum(PyObject *self, PyObject *args) {
char *char_nums;
if (!PyArg_ParseTuple(args, "s", &char_nums)) {
return NULL;
}
unsigned long fact_sum;
fact_sum = cfactorial_sum(char_nums);
return Py_BuildValue("i", fact_sum);
}
static PyObject *DemoLib_iFactorialSum(PyObject *self, PyObject *args) {
PyObject *lst;
if (!PyArg_ParseTuple(args, "O", &lst)) {
return NULL;
}
int n = PyObject_Length(lst);
if (n < 0) {
return NULL;
}
long nums(n);
for (int i = 0; i < n; i++) {
PyLongObject *item = PyList_GetItem(lst, i);
long num = PyLong_AsLong(item);
nums(i) = num;
}
unsigned long fact_sum;
fact_sum = ifactorial_sum(nums, n);
return Py_BuildValue("i", fact_sum);
}
static PyMethodDef DemoLib_FunctionsTable() = {
{
"sfactorial_sum",
DemoLib_cFactorialSum,
METH_VARARGS,
"Calculates factorial sum from digits in string of numbers"
}, {
"ifactorial_sum",
DemoLib_iFactorialSum,
METH_VARARGS,
"Calculates factorial sum from list of ints"
}, {
NULL, NULL, 0, NULL
}
};
static struct PyModuleDef DemoLib_Module = {
PyModuleDef_HEAD_INIT,
"demo",
"Demo Python wrapper for custom C extension library.",
-1,
DemoLib_FunctionsTable
};
PyMODINIT_FUNC PyInit_demo(void) {
return PyModule_Create(&DemoLib_Module);
}
بسته بندی و ساخت پسوند
اکنون افزونه را بسته بندی و می سازم تا بتوانم با کمک آن در پایتون از آن استفاده کنم ابزارهای نصب کتابخانه
اولین کاری که باید انجام دهم نصب است setuptools
:
$ pip install setuptools
حالا یک فایل جدید به نام می سازم setup.py
. در زیر نمایشی از روش سازماندهی فایل های من است:
├── demolib.c
├── demolib.h
├── demomodule.c
└── setup.py
داخل setup.py
کد زیر را که وارد می کند قرار دهید Extension
کلاس و تابع راه اندازی از setuptools
. من نمونه ای از Extension
کلاسی که برای کامپایل کد C با استفاده از GCC کامپایلر، که به صورت بومی نصب شده است روی اکثر سیستم عامل های سبک یونیکس کاربران ویندوز می خواهند نصب کنند MinGW.
آخرین بیت کد نشان داده شده به سادگی از حداقل اطلاعات پیشنهادی برای بسته بندی کد در یک بسته پایتون عبور می کند.
from setuptools import Extension, setup
module = Extension("demo",
sources=(
'demolib.c',
'demomodule.c'
))
setup(name='demo',
version='1.0',
description='Python wrapper for custom C extension',
ext_modules=(module))
در یک پوسته، دستور زیر را برای ساختن و نصب بسته بر روی سیستم خود اجرا می کنم. این کد مکان را پیدا می کند setup.py
فایل و آن را فراخوانی کنید setup(...)
تابع:
$ pip install .
در نهایت، اکنون می توانم یک مفسر پایتون را راه اندازی کنم، import ماژول من و تست توابع برنامه افزودنی من:
$ python
Python 3.6.4 |Anaconda, Inc.| (default, Dec 21 2017, 15:39:08)
>>> import demo
>>> demo.sfactorial_sum("12345")
153
>>> demo.ifactorial_sum((1,2,3,4,5))
153
>>>
نتیجه
در صحبتهای پایانیام میخواهم بگویم که این آموزش واقعاً به سختی سطح API Python C را خراش میدهد، که به نظر من موضوعی بزرگ و دلهرهآور است. امیدوارم اگر نیاز به گسترش پایتون پیدا کردید، این آموزش همراه با اسناد رسمی به شما در دستیابی به آن هدف کمک می کند.
ممنون که خواندید و از هر نظر یا انتقادی در زیر استقبال می کنم.
(برچسبها به ترجمه)# python
منتشر شده در 1403-01-28 12:06:03