تعریف ماژول‌های توسعه‌ای

یک افزونه C برای سی‌پایتون یک کتابخانه اشتراکی است (برای مثال، یک پرونده .so در لینوکس، DLL .pyd در ویندوز) که در فرایند پایتون بارگذاری‌پذیر است (برای مثال، با تنظیمات سازگار کامپایلر کامپایل شده است) و یک تابع مقداردهی اولیه را اکسپورت می‌کند.

برای اینکه به‌صورت پیش‌فرض ایمپورت‌پذیر باشد (یعنی توسط importlib.machinery.ExtensionFileLoader)، کتابخانه‌ی اشتراکی باید در sys.path موجود باشد و باید بر اساس نام ماژول به‌علاوه‌ی یکی از پسوند‌های فهرست‌شده در importlib.machinery.EXTENSION_SUFFIXES نام‌گذاری شود.

توجه

ساخت، بسته‌بندی و توزیع ماژول‌های توسعه‌ای بهتر است با ابزارهای شخص ثالث انجام شود و خارج از محدوده‌ی این سند است. یکی از ابزارهای مناسب، Setuptools است که می‌توانید مستندات آن را در https://setuptools.pypa.io/en/latest/setuptools.html بیابید.

به‌طور معمول، تابع مقداردهی اولیه، تعریف ماژولی را برمی‌گرداند که با استفاده از PyModuleDef_Init() مقداردهی اولیه شده است. این اجازه می‌دهد فرایند ایجاد به چندین مرحله تقسیم شود:

  • پیش از آنکه هر کد قابل توجهی اجرا شود، پایتون می‌تواند تعیین کند که ماژول از چه قابلیت‌هایی پشتیبانی می‌کند، و می‌تواند محیط را تنظیم کند یا از بارگذاری یک ماژول توسعه‌ای ناسازگار خودداری کند.

  • به‌طور پیش‌فرض، خودِ پایتون شیء ماژول را ایجاد می‌کند -- یعنی همان کاری را انجام می‌دهد که object.__new__() برای کلاس‌ها انجام می‌دهد. همچنین ویژگی‌های اولیه‌ای مانند __package__ و __loader__ را تنظیم می‌کند.

  • پس از آن، شیء ماژول با استفاده از کد مخصوص توسعه مقداردهی اولیه می‌شود — معادلِ __init__() در کلاس‌ها.

این روش مقداردهی اولیه چندمرحله‌ای (multi-phase initialization) نامیده می‌شود تا از طرح قدیمی (اما همچنان پشتیبانی‌شده) مقداردهی اولیه تک‌مرحله‌ای (single-phase initialization) متمایز باشد؛ در این طرح، تابع مقداردهی اولیه ماژولی کاملاً ساخته‌شده را برمی‌گرداند. برای جزئیات، بخش مقداردهی اولیه تک‌مرحله‌ای در پایین را ببینید.

تغییر یافته در نسخه‌ی 3.5: پشتیبانی از مقداردهی اولیه چندمرحله‌ای اضافه شد (PEP 489).

نمونه‌های متعدد ماژول

به‌طور پیش‌فرض، ماژول‌های توسعه‌ای تک‌نمونه نیستند. برای مثال، اگر ورودی sys.modules حذف شود و ماژول دوباره ایمپورت شود، یک شیء ماژول جدید ساخته می‌شود که معمولاً با اشیاء متد و نوع تازه پر می‌شود. ماژول قدیمی مشمول زباله‌روبی معمول می‌شود. این، بازتاب رفتار ماژول‌های پایتون خالص است.

ممکن است نمونه‌های اضافی ماژول در زیرمفسرها یا پس از راه‌اندازی مجدد ران‌تایم پایتون (Py_Finalize() و Py_Initialize()) ایجاد شوند. در این موارد، اشتراک‌گذاری اشیاء پایتون بین نمونه‌های ماژول به احتمال زیاد باعث فروپاشی یا رفتار تعریف‌نشده می‌شود.

برای پرهیز از چنین مشکلاتی، هر نمونه از یک ماژول توسعه‌ای باید مجزا باشد: تغییرات در یک نمونه نباید به‌طور ضمنی بر نمونه‌های دیگر تأثیر بگذارد، و تمام وضعیت‌های متعلق به ماژول، از جمله ارجاع‌ها به اشیاء پایتون، باید مختص به یک نمونه ماژول خاص باشند. برای جزئیات بیشتر و راهنمای عملی، جداسازی ماژول‌های توسعه را ببینید.

راه ساده‌تر برای اجتناب از این مشکلات، ایجاد خطا هنگام مقداردهی اولیه‌ی مکرر است.

انتظار می‌رود تمام ماژول‌ها از زیرمفسرها پشتیبانی کنند، یا در غیر این صورت، به‌صراحت عدم پشتیبانی خود را اعلام کنند. این کار معمولاً از طریق جداسازی یا مسدود کردن مقداردهی اولیه‌ی مکرر، همان‌طور که در بالا ذکر شد، انجام می‌شود. همچنین ممکن است یک ماژول با استفاده از جایگاه Py_mod_multiple_interpreters به مفسر اصلی محدود شود.

تابع مقداردهی اولیه

تابع مقدار‌دهی اولیه‌ای که توسط یک ماژول توسعه‌ای تعریف می‌شود، امضای زیر را دارد:

PyObject *PyInit_modulename(void)

نام آن باید PyInit_<name> باشد، که در آن <name> با نام ماژول جایگزین می‌شود.

برای ماژول‌هایی با نام‌های فقط اسکی، تابع باید در عوض PyInit_<name> نام‌گذاری شود، که در آن <name> با نام ماژول جایگزین می‌شود. هنگام استفاده از مقداردهی اولیه چندمرحله‌ای، نام‌های غیراسکی برای ماژول‌ها مجاز هستند. در این حالت، نام تابع مقداردهی اولیه PyInitU_<name> است، که در آن <name> با استفاده از کدگذاری punycode پایتون کدگذاری می‌شود و خط تیره‌ها با زیرخط جایگزین می‌شوند. در پایتون:

def initfunc_name(name):
    try:
        suffix = b'_' + name.encode('ascii')
    except UnicodeEncodeError:
        suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
    return b'PyInit' + suffix

توصیه می‌شود که تابع مقداردهی اولیه را با استفاده از یک ماکرو کمکی تعریف کنید:

PyMODINIT_FUNC

یک تابع مقداردهی اولیه‌ی ماژول توسعه‌ای را اعلان می‌کند. این ماکرو:

  • نوع بازگشتی PyObject* را تعیین می‌کند،

  • هرگونه اعلان پیوند خاص مورد نیاز پلتفرم را می‌افزاید و

  • برای C++، تابع را به‌صورت extern "C" اعلان می‌کند.

برای مثال، ماژولی به نام spam به شکل زیر تعریف می‌شود:

static struct PyModuleDef spam_module = {
    .m_base = PyModuleDef_HEAD_INIT,
    .m_name = "spam",
    ...
};

PyMODINIT_FUNC
PyInit_spam(void)
{
    return PyModuleDef_Init(&spam_module);
}

با تعریف چند تابع مقداردهی اولیه می‌توان چند ماژول را از یک کتابخانه اشتراکی واحد اکسپورت کرد. با این حال، ایمپورت کردن آن‌ها مستلزم استفاده از پیوند‌های نمادین یا یک ایمپورت‌کننده سفارشی است، زیرا به‌طور پیش‌فرض تنها تابع متناظر با نام پرونده پیدا می‌شود. برای جزئیات، به بخش چند ماژول در یک کتابخانه در PEP 489 مراجعه کنید.

تابع مقداردهی اولیه معمولاً تنها آیتم غیرstatic تعریف‌شده در کد منبع C ماژول است.

مقداردهی اولیه چندمرحله‌ای

معمولاً، تابع مقدار‌دهی اولیه (PyInit_modulename) نمونه‌ای از PyModuleDef برمی‌گرداند که m_slots آن غیر NULL است. پیش از آنکه بازگردانده شود، نمونه‌ی PyModuleDef باید با استفاده از تابع زیر مقدار‌دهی اولیه شود:

PyObject *PyModuleDef_Init(PyModuleDef *def)
قسمتی از ABI پایدار از نسخه‌ی 3.5.

اطمینان حاصل می‌کند که تعریف ماژول، یک شیء پایتونِ به‌درستی مقداردهی‌شده است که نوع و شمارش ارجاع خود را به‌درستی گزارش می‌کند.

def قالب‌ریزی‌شده به PyObject* را برمی‌گرداند، یا NULL را در صورت وقوع خطا.

فراخوانی این تابع برای مقداردهی اولیه چندمرحله‌ای لازم است. این تابع نباید در زمینه‌های دیگر استفاده شود.

توجه داشته باشید که پایتون فرض می‌کند ساختارهای PyModuleDef به‌صورت ایستا تخصیص یافته‌اند. این تابع ممکن است یک ارجاع جدید یا یک ارجاع امانتی برگرداند؛ این ارجاع نباید آزاد شود.

اضافه شده در نسخه‌ی 3.5.

مقداردهی اولیه تک‌مرحله‌ای قدیمی

دقت

راه‌اندازی تک‌فازی (single-phase initialization) سازوکاری قدیمی برای راه‌اندازی ماژول‌های توسعه‌ای است و معایب شناخته‌شده و نقص‌های طراحی دارد. به نویسندگان ماژول‌های توسعه‌ای توصیه می‌شود که به‌جای آن از راه‌اندازی چندفازی (multi-phase initialization) استفاده کنند.

در مقداردهی اولیه تک‌فازی، تابع مقداردهی اولیه (PyInit_modulename) باید یک شیء ماژول ایجاد کند، آن را پر کند و بازگرداند. این کار معمولاً با استفاده از PyModule_Create() و توابعی مانند PyModule_AddObjectRef() انجام می‌شود.

مقداردهی اولیه تک‌فازی در موارد زیر با پیش‌فرض تفاوت دارد:

  • ماژول‌های تک‌فاز (single-phase) «تک‌نمونه» هستند، یا به بیان دقیق‌تر، حاوی «تک‌نمونه» هستند.

    هنگامی که ماژول برای نخستین بار مقداردهی اولیه می‌شود، پایتون محتویات __dict__ ماژول را ذخیره می‌کند (یعنی، به‌طور معمول، توابع و نوع‌های ماژول).

    برای ایمپورت‌های بعدی، پایتون تابع مقداردهی اولیه را دوباره فراخوانی نمی‌کند. در عوض، شیء ماژول جدیدی با __dict__ جدید می‌سازد و محتویات ذخیره‌شده را در آن کپی می‌کند. برای مثال، با فرض یک ماژول تک‌فاز _testsinglephase [1] که تابعی به نام sum و کلاس استثنایی به نام error را تعریف می‌کند:

    >>> import sys
    >>> import _testsinglephase as one
    >>> del sys.modules['_testsinglephase']
    >>> import _testsinglephase as two
    >>> one is two
    False
    >>> one.__dict__ is two.__dict__
    False
    >>> one.sum is two.sum
    True
    >>> one.error is two.error
    True
    

    رفتار دقیق باید به‌عنوان جزئیات پیاده‌سازی سی‌پایتون در نظر گرفته شود.

  • برای دور زدن این واقعیت که PyInit_modulename آرگومان مشخصات نمی‌پذیرد، بخشی از وضعیت سازوکار ایمپورت ذخیره می‌شود و بر نخستین ماژول مناسب ایجاد‌شده در طول فراخوانی PyInit_modulename اعمال می‌شود. به‌طور خاص، هنگامی که یک زیرماژول ایمپورت می‌شود، این سازوکار نام بسته والد را به ابتدای نام ماژول می‌افزاید.

    یک تابع تک‌فازه PyInit_modulename باید شیء ماژول «خود» را در اسرع وقت ایجاد کند، پیش از آن‌که بتوان هر شیء ماژول دیگری را ایجاد کرد.

  • نام‌های ماژول غیراسکی (PyInitU_modulename) پشتیبانی نمی‌شوند.

  • ماژول‌های تک‌فازی از توابع جستجوی ماژول مانند PyState_FindModule() پشتیبانی می‌کنند.