پرسش‌های متداول درباره‌ی گسترش/تعبیه

آیا می‌توانم توابع خود را در C بسازم؟

بله، شما می‌توانید ماژول‌های توکار حاوی توابع، متغیرها، استثناها و حتی انواع جدید را در C ایجاد کنید. این موضوع در سند توسعه و تعبیه مفسر پایتون توضیح داده شده است.

بیشتر کتاب‌های سطح متوسط یا پیشرفته پایتون نیز به این موضوع می‌پردازند.

آیا می‌توانم توابع خودم را در C++ بسازم؟

بله، با استفاده از ویژگی‌های سازگاری با C موجود در C++. extern "C" { ... } را اطراف پرونده‌های include پایتون قرار دهید و extern "C" را پیش از هر تابعی که قرار است توسط مفسر پایتون فراخوانی شود، قرار دهید. اشیاء سراسری یا ایستای C++ دارای سازنده احتمالاً ایده‌ی خوبی نیستند.

نوشتن C دشوار است؛ آیا جایگزین‌هایی وجود دارد؟

بسته به این‌که شما می‌خواهید چه کاری انجام دهید، جایگزین‌های متعددی برای نوشتن افزونه‌های C خودتان وجود دارد. ابزارهای شخص ثالث توصیه‌شده هم رویکردهایی ساده‌تر و هم پیشرفته‌تر برای ایجاد افزونه‌های C و C++ برای پایتون ارائه می‌دهند.

چگونه می‌توانم دستورات دلخواه پایتون را از C اجرا کنم؟

بالاترین تابع از نظر سطح برای انجام این کار PyRun_SimpleString() است که یک آرگومان رشته‌ای واحد می‌گیرد تا در زمینه‌ی ماژول __main__ اجرا شود و در صورت موفقیت 0 و در صورت بروز استثنا (از جمله SyntaxError) -1 برمی‌گرداند. اگر کنترل بیشتری می‌خواهید، از PyRun_String() استفاده کنید؛ کد منبع PyRun_SimpleString() را در Python/pythonrun.c ببینید.

چگونه می‌توانم یک عبارت پایتون دلخواه را از C ارزیابی کنم؟

تابع PyRun_String() را از پرسش قبلی با نماد آغازین Py_eval_input فراخوانی کنید؛ این تابع یک عبارت را تجزیه می‌کند، آن را ارزیابی می‌کند و مقدار آن را برمی‌گرداند.

چگونه می‌توانم مقادیر C را از یک شیء پایتون استخراج کنم؟

این به نوع شیء بستگی دارد. اگر یک تاپل باشد، PyTuple_Size() طول آن را برمی‌گرداند و PyTuple_GetItem() آیتم در یک اندیس مشخص را برمی‌گرداند. فهرست‌ها توابع مشابهی دارند، PyList_Size() و PyList_GetItem().

برای بایت‌ها، PyBytes_Size() طول آن را برمی‌گرداند و PyBytes_AsStringAndSize() اشاره‌گری به مقدار و طول آن در اختیار قرار می‌دهد. توجه داشته باشید که اشیاء بایت پایتون ممکن است حاوی بایت‌های null باشند، بنابراین نباید از strlen() در C استفاده شود.

برای بررسی نوع یک شیء، ابتدا مطمئن شوید که NULL نیست، سپس از PyBytes_Check()، PyTuple_Check()، PyList_Check() و غیره استفاده کنید.

همچنین یک API سطح بالا برای اشیای پایتون وجود دارد که توسط رابط به‌اصطلاح «انتزاعی» ارائه می‌شود — برای جزئیات بیشتر Include/abstract.h را بخوانید. این رابط امکان کار با هر نوع دنباله‌ی پایتون را از طریق فراخوانی‌هایی مانند PySequence_Length()، PySequence_GetItem() و غیره، و همچنین بسیاری از پروتکل‌های مفید دیگر مانند اعداد (PyNumber_Index() و غیره) و نگاشت‌ها در APIهای PyMapping فراهم می‌کند.

چگونه از Py_BuildValue() برای ایجاد تاپلی با طول دلخواه استفاده کنم؟

نمی‌توانید. به‌جای آن از PyTuple_Pack() استفاده کنید.

چگونه می‌توانم متد یک شیء را از C فراخوانی کنم؟

تابع PyObject_CallMethod() می‌تواند برای فراخوانی یک متد دلخواه از یک شیء استفاده شود. پارامترها عبارت‌اند از شیء، نام متد برای فراخوانی، یک رشته قالب مانند آنچه با Py_BuildValue() استفاده می‌شود، و مقادیر آرگومان:

PyObject *
PyObject_CallMethod(PyObject *object, const char *method_name,
                    const char *arg_format, ...);

این برای هر شیئی که متد دارد، چه توکار و چه تعریف‌شده توسط کاربر، کار می‌کند. شما مسئولید که در نهایت مقدار بازگشتی را Py_DECREF() کنید.

برای فراخوانی متد "seek" یک شیء پرونده، مثلاً با آرگومان‌های ۱۰، ۰ (با فرض اینکه اشاره‌گر شیء پرونده "f" باشد):

res = PyObject_CallMethod(f, "seek", "(ii)", 10, 0);
if (res == NULL) {
        ... an exception occurred ...
}
else {
        Py_DECREF(res);
}

توجه داشته باشید که از آن‌جا که PyObject_CallObject() همیشه برای فهرست آرگومان‌ها یک تاپل می‌خواهد، برای فراخوانی یک تابع بدون آرگومان، "()" را به‌عنوان قالب ارسال کنید، و برای فراخوانی یک تابع با یک آرگومان، آرگومان را داخل پرانتز قرار دهید، برای مثال "(i)".

چگونه می‌توانم خروجی PyErr_Print() (یا هر چیزی که به stdout/stderr چاپ می‌شود) را دریافت کنم؟

در کد پایتون، شیء‌ای تعریف کنید که از متد write() پشتیبانی می‌کند. این شیء را به sys.stdout و sys.stderr اختصاص دهید. print_error را فراخوانی کنید، یا فقط اجازه دهید سازوکار استاندارد ردگیری پشته کار کند. سپس، خروجی به هر جایی می‌رود که متد write() شما آن را ارسال می‌کند.

ساده‌ترین راه برای انجام این کار استفاده از کلاس io.StringIO است:

>>> import io, sys
>>> sys.stdout = io.StringIO()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(sys.stdout.getvalue())
foo
hello world!

یک شیء سفارشی برای انجام همان کار به این شکل خواهد بود:

>>> import io, sys
>>> class StdoutCatcher(io.TextIOBase):
...     def __init__(self):
...         self.data = []
...     def write(self, stuff):
...         self.data.append(stuff)
...
>>> import sys
>>> sys.stdout = StdoutCatcher()
>>> print('foo')
>>> print('hello world!')
>>> sys.stderr.write(''.join(sys.stdout.data))
foo
hello world!

چگونه از C به یک ماژول نوشته‌شده با Python دسترسی پیدا کنم؟

می‌توانید یک اشاره‌گر به شیء ماژول را به صورت زیر دریافت کنید:

module = PyImport_ImportModule("<modulename>");

اگر ماژول هنوز ایمپورت‌نشده باشد (یعنی هنوز در sys.modules موجود نباشد)، این، ماژول را مقداردهی اولیه می‌کند؛ در غیر این صورت، صرفاً مقدار sys.modules["<modulename>"] را برمی‌گرداند. توجه داشته باشید که این، ماژول را وارد هیچ فضای نامی نمی‌کند -- فقط اطمینان حاصل می‌کند که ماژول مقداردهی اولیه شده و در sys.modules ذخیره شده است.

سپس می‌توانید به ویژگی‌های ماژول (یعنی هر نام تعریف‌شده در ماژول) به صورت زیر دسترسی داشته باشید:

attr = PyObject_GetAttrString(module, "<attrname>");

فراخوانی PyObject_SetAttrString() برای انتساب به متغیرهای درون ماژول نیز کار می‌کند.

چگونه از پایتون با اشیاء C++ رابط برقرار کنم؟

بسته به نیازمندی‌های شما، رویکردهای زیادی وجود دارد. برای انجام این کار به‌صورت دستی، با خواندن سند "Extending and Embedding" آغاز کنید. توجه داشته باشید که از نظر سیستم ران‌تایم پایتون، تفاوت چندانی میان C و C++ وجود ندارد — بنابراین راهبرد ساختن یک نوع جدید پایتون بر پایه‌ی یک نوع ساختار C (اشاره‌گر) برای اشیاء C++ نیز کار خواهد کرد.

برای کتابخانه‌های C++، نوشتن C دشوار است؛ آیا جایگزین‌هایی وجود دارد؟ را ببینید.

من یک ماژول را با استفاده از پرونده Setup اضافه کردم و make شکست می‌خورد؛ چرا؟

Setup باید با یک خط جدید پایان یابد؛ اگر خط جدیدی در آنجا نباشد، فرآیند ساخت شکست می‌خورد. (رفع این مشکل به چند ترفند زشت اسکریپت پوسته نیاز دارد، و این باگ آن‌قدر جزئی است که به نظر نمی‌رسد ارزش تلاش داشته باشد.)

چگونه یک افزونه را اشکال‌زدایی کنم؟

هنگام استفاده از GDB با افزونه‌های بارگذاری‌شده به‌صورت پویا، تا زمانی که افزونه‌ی شما بارگذاری نشود، نمی‌توانید نقطه توقفی در افزونه‌ی خود تنظیم کنید.

در پرونده .gdbinit خود (یا به‌صورت تعاملی)، این دستور را اضافه کنید:

br _PyImport_LoadDynamicModule

سپس، هنگامی که GDB را اجرا می‌کنید:

$ gdb /local/bin/python
gdb) run myscript.py
gdb) continue # repeat until your extension is loaded
gdb) finish   # so that your extension is loaded
gdb) br myfunction.c:50
gdb) continue

می‌خواهم یک ماژول پایتون را روی سیستم لینوکسی خود کامپایل کنم، اما برخی پرونده‌ها وجود ندارند. چرا؟

بیشتر نسخه‌های بسته‌بندی‌شده‌ی پایتون، برخی از پرونده‌های مورد نیاز برای کامپایل افزونه‌های پایتون را شامل نمی‌شوند.

برای Red Hat، بسته‌ی RPM با نام python3-devel را نصب کنید تا پرونده‌های ضروری را دریافت کنید.

برای دبیان، apt-get install python3-dev را اجرا کنید.

چگونه «ورودی ناقص» را از «ورودی نامعتبر» تشخیص دهم؟

گاهی ممکن است بخواهید رفتار مفسر تعاملی پایتون را شبیه‌سازی کنید، که در آن وقتی ورودی ناقص است (مثلاً شروع یک دستور "if" را تایپ کرده‌اید یا پرانتزها یا علامت‌های نقل‌قول سه‌گانه‌ی رشته را نبسته‌اید)، یک اعلان ادامه به شما می‌دهد، اما وقتی ورودی نامعتبر است، بلافاصله یک پیام خطای سینتکس به شما می‌دهد.

در پایتون می‌توانید از ماژول codeop استفاده کنید، که رفتار پارسر را به‌اندازه‌ی کافی تقریب می‌زند. برای مثال، IDLE از این استفاده می‌کند.

ساده‌ترین راه برای انجام این کار در C این است که PyRun_InteractiveLoop() را فراخوانی کنید (احتمالاً در یک نخ جداگانه) و اجازه دهید مفسر پایتون ورودی را برای شما مدیریت کند. همچنین می‌توانید PyOS_ReadlineFunctionPointer() را طوری تنظیم کنید که به تابع ورودی سفارشی شما اشاره کند. برای نکات بیشتر، Modules/readline.c و Parser/myreadline.c را ببینید.

چگونه می‌توانم نمادهای تعریف‌نشده‌ی g++ یعنی __builtin_new یا __pure_virtual را پیدا کنم؟

برای بارگذاری پویای ماژول‌های توسعه‌ای g++، باید پایتون را دوباره کامپایل کنید، آن را با استفاده از g++ دوباره لینک کنید (LINKCC را در Makefile ماژول‌های پایتون تغییر دهید) و ماژول توسعه‌ای خود را با استفاده از g++ لینک کنید (برای مثال، g++ -shared -o mymodule.so mymodule.o).

آیا می‌توانم یک کلاس شیء بسازم که برخی از متدهای آن در C و برخی دیگر در Python پیاده‌سازی‌شده باشند (برای مثال از طریق وراثت)؟

بله، شما می‌توانید از کلاس‌های توکار مانند int، list، dict و غیره ارث‌بری کنید.

کتابخانه Boost Python (BPL، https://www.boost.org/libs/python/doc/index.html) راهی برای انجام این کار از C++ فراهم می‌کند (یعنی می‌توانید با استفاده از BPL از یک کلاس توسعه نوشته‌شده به C++ ارث‌بری کنید).