مهاجرت به ABI پایدار برای نخ‌بندی آزاد (abi3t)

شروع از نسخه‌ی 3.15، سی‌پایتون از گونه‌ای از ABI پایدار پشتیبانی می‌کند که از پایتون نخ‌بندی‌آزاد پشتیبانی می‌کند: ABI پایدار برای ساخت‌های نخ‌آزاد، یا به اختصار abi3t. این سند نحوه سازگار کردن افزونه‌های C API را برای پشتیبانی از نخ‌بندی آزاد شرح می‌دهد.

چرا این کار را انجام دهیم

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

بدون ABI پایدار، باید برای هر نسخه‌ی ویژگی سی‌پایتونی که می‌خواهید از آن پشتیبانی کنید، یک کتابخانه‌ی مشترک جداگانه و معمولاً یک توزیع ویل (wheel) بساحید. برای مثال، هر برچسب در جدول زیر نمایانگر یک کتابخانه/ویل جداگانه است:

نسخه‌ی سی‌پایتون

غیر نخ‌آزاد

نخ‌آزاد

3.12

cpython-312

---

3.13

cpython-313

cpython-313t

3.14

cpython-314

cpython-314t

3.15

cpython-315

cpython-315t

۳.۱۶

cpython-316

cpython-316t

نسخه‌های بعدی

cpython-3XX

cpython-3XXt

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

با استفاده از Stable ABI (معادل abi3، که در سی‌پایتون ۳.۲ معرفی شد)، یک افزونەی واحد (به ازای هر پلتفرم) می‌تواند تمام ساخت‌های غیر نخ‌آزاد سی‌پایتون را پوشش دهد:

نسخه‌ی سی‌پایتون

غیر نخ‌آزاد

نخ‌آزاد

3.12

abi3

---

3.13

cpython-313t

3.14

cpython-314t

3.15

cpython-315t

۳.۱۶

cpython-316t

نسخه‌های بعدی

cpython-3XXt

Stable ABI برای ساخت‌های نخ‌آزاد (abi3t) که در سی‌پایتون ۳.۱۵ معرفی شد، همین کار را برای ساخت‌های نخ‌آزاد انجام می‌دهد. همچنین با نسخه‌های غیر نخ‌آزاد نیز سازگار است:

نسخه‌ی سی‌پایتون

غیر نخ‌آزاد

نخ‌آزاد

3.12

abi3 *

---

3.13

cpython-313t

3.14

cpython-314t

3.15

abi3t

۳.۱۶

نسخه‌های بعدی

* (همان‌طور که در بالا ذکر شد، افزونەی abi3 با تمام ساخت‌های غیر نخ‌آزاد سازگار است؛ حتی نسخه‌های ۳.۱۵ به بالا که این جدول آن‌ها را به abi3t «نسبت می‌دهد».)

چرا این کار را نکنیم

Stable ABI دو نقطه‌ی ضعف اصلی دارد.

نخست، ممکن است افزونەی شما کندتر شود، زیرا Stable ABI سازگاری را بر کارایی ترجیح می‌دهد. این تفاوت معمولاً محسوس نیست و اغلب می‌توان با استفاده از یک سورس واحد برای ساخت هم یک ساخت Stable ABI و هم چند ساختِ وابستہ به نسخه برای نسخه‌های «رده ۱» سی‌پایتون، آن را کاهش داد.

دوم، همه‌ی C API در دسترس نیست. افزونه‌ها باید برای ساخته شدن با Stable ABI پورت شوند، که ممکن است دشوار یا در موارد نادر، غیرممکن باشد.

به‌طور خاص، abi3t به APIهای اضافه‌شده در سی‌پایتون ۳.۱۵ نیاز دارد. اگر می‌خواهید افزونەی خود را برای نسخه‌های قدیمی‌تر سی‌پایتون از همان سورس بسازید، دو گزینه‌ی اصلی پیش رو دارید:

  • استفاده از شرط‌های پیش‌پردازنده (preprocessor conditionals).

    هنگام پیروی از این راهنما، هر زمان که به شما گفته شد تغییری انجام دهید که ساخت را در نسخه‌های سی‌پایتون مورد نظرتان دچار مشکل می‌کند، از بلوک‌های #ifdef Py_TARGET_ABI3T استفاده کنید. کد از پیش موجود را در بلوک‌های #else نگه دارید.

    برای افزونه‌های C دست‌نویس، این رویکرد تا سی‌پایتون ۳.۱۲ منطقی است، به دلیل اضافه‌سازی‌های معرفی‌شده در PEP 697. حفظ سازگاری با نسخه‌ی ۳.۱۱ و پایین‌تر ممکن است برای تولیدگرهای کد (مثلاً Cython) ارزش داشته باشد.

  • تا زمانی که نتوانید پشتیبانی از نسخه‌های قدیمی‌تر را کنار بگذارید، به abi3t منتقل نکنید و به ساخت افزونه‌های جداگانه برای هر نسخه‌ی سی‌پایتون ادامه دهید.

    این یک رویکرد معتبر است. لازم نیست همه‌ی افزونه‌ها در حال حاضر به abi3t تغییر وضعیت دهند.

پیش‌نیازها

این راهنما فرض می‌کند که شما یک افزونه دارید که مستقیماً به زبان C (یا ++C) نوشته شده است و می‌خواهید آن را به abi3t منتقل کنید.

اگر افزونه‌ی شما از یک تولیدگر کد (مانند Cython) یا اتصال زبانی (مانند PyO3) استفاده می‌کند، بهتر است صبر کنید تا آن ابزار از abi3t پشتیبانی کند. اگر چنین ابزاری را نگهداری می‌کنید، ممکن است بتوانید دستورالعمل‌های اینجا را برای ابزار خود تطبیق دهید.

ABI پایدار غیر نخ‌آزاد

افزونه‌ی شما باید از ABI پایدار غیر نخ‌آزاد (abi3) پشتیبانی کند. اگر این‌طور نیست، یا ابتدا آن را منتقل کنید، یا این راهنما را دنبال کنید اما آماده باشید تا مسائلی را که در آن ذکر نشده است، برطرف کنید.

پشتیبانی از نخ‌بندی آزاد

اگرچه از نظر فنی یک پیش‌نیاز سخت‌گیرانه نیست، اما احتمالاً ترجیح می‌دهید پیش از انتقال افزونه‌ی خود به abi3t، آن را برای نخ‌بندی آزاد آماده کنید. برای دستورالعمل‌ها به پشتیبانی افزونه‌های C API از نخ‌بندی آزاد مراجعه کنید.

همچنین ببینید

انتقال ماژول‌های افزونه برای پشتیبانی از نخ‌بندی آزاد: یک راهنمای انتقال که توسط کامیونیتی برای توسعه‌دهندگان افزونه نگهداری می‌شود.

ایزوله‌سازی ماژول‌های افزونه

ماژول شما باید از راه‌اندازی چندفازی استفاده کند و یا باید ایزوله باشد یا بارگذاری خود را به حداکثر یک بار در هر فرایند محدود کند. اگر این‌گونه نیست، ابتدا جداسازی ماژول‌های توسعه را دنبال کنید. (برای میانبر، به بخش انصراف مراجعه کنید.)

جلوگیری از انواعی با اندازه‌ی متغیر

اگر افزونه‌ی شما انواعی با اندازه‌ی متغیر را تعریف می‌کند (با استفاده از Py_tp_itemsize یا PyTypeObject.tp_itemsize)، نمی‌تواند به abi3t ۳.۱۵ منتقل شود.

تنظیم ساخت

اگر از ابزار ساختی استفاده می‌کنید، مستندات آن را برای عبارت «abi3t» جستجو کنید و هرگونه دستورالعملی را برای انتخاب ABI دنبال کنید. برای ارجاع، در اینجا پیوندهای مستقیمی برای چند ابزار ساخت محبوب آورده شده است:

ممکن است بخواهید با افزودن موقت کد زیر درست پس از #include <Python.h> تأیید کنید که ابزار پرچم درست را تنظیم کرده است:

#if Py_TARGET_ABI3T+0 <= 0x30f0000
#error "abi3t define is not set!"
#endif

این امر باید خطایی متفاوت از "abi3t define is not set" ایجاد کند.

همچنین ببینید

Building and distributing abi3t extensions: پیکربندی ساخت و نمونه‌های آزمایش ویل (wheel) در راهنمای پایتون بدون قفل (Free-Threading) که توسط کامیونیتی نگهداری می‌شود.

نکته

اگر ابزار ساخت شما هنوز از abi3t پشتیبانی نمی‌کند، کلان‌دستور زیر را پیش از گنجاندن Python.h تنظیم کنید:

#define Py_TARGET_ABI3T 0x30f0000

یا آن را به‌عنوان پرچم کامپایلر مشخص کنید، برای نمونه:

-DPy_TARGET_ABI3T=0x30f0000

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

اگر این کلان‌دستور را به‌صورت دستی تنظیم کنید، بعدها باید افزونه‌ی حاصل را نیز به‌صورت دستی نام‌گذاری و برچسب‌گذاری کنید. این موضوع در برچسب‌گذاری و توزیع در ادامه پوشش داده شده است.

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

قلاب اکسپورت ماژول

مگر اینکه قبلاً این مرحله را انجام داده باشید، ماژول توسعه‌ای شما یک تابع مقداردهی اولیه‌ی ماژول به نام PyInit_<modname> (که در آن modname نام ماژول شما است) تعریف می‌کند. شما باید آن را به یک هوک صادر کردن ماژول به نام PyModExport_<modname> منتقل کنید که این ویژگی در نسخه‌ی ۳.۱۵ سی‌پایتون در PEP 793 اضافه شده است.

تابع اولیه‌ی موجود شما باید به شکل زیر باشد (با نام‌های دلخواه خودتان برای <modname> و <moddef>):

PyMODINIT_FUNC
PyInit_<modname>(void)
{
    return PyModuleDef_Init(&<moddef>);
}

اگر کدی پیش از return وجود دارد، آن را به یک تابع جایگاه Py_mod_create یا Py_mod_exec منتقل کنید. برای اطلاعات مرتبط، مستندات PyInit documentation را ببینید.

این تابع به یک شیء PyModuleDef (که در کد بالا <moddef> است) ارجاع می‌دهد. تعریف آن باید مشابه موارد زیر باشد، با مقادیر متفاوت و احتمالاً برخی فیلدهای بدون نام یا حذف‌شده:

static PyModuleDef <moddef> = {
    PyModuleDef_HEAD_INIT,
    .m_name = "my_module",
    .m_doc = "my docstring",
    .m_size = sizeof(my_state_struct),
    .m_methods = my_methods,
    .m_slots = my_slots,
    .m_traverse = my_traverse,
    .m_clear = my_clear,
    .m_free = my_free,
};

این تعریف و تابع PyInit را حذف کنید (یا آن‌ها را در یک بلوک #ifndef Py_TARGET_ABI3T قرار دهید تا سازگاری با نسخه‌های قبلی حفظ شود) و موارد زیر را جایگزین آن‌ها کنید:

PyABIInfo_VAR(abi_info);

static PySlot my_slot_array[] = {
    PySlot_STATIC_DATA(Py_mod_abi, &abi_info),
    PySlot_STATIC_DATA(Py_mod_name, "my_module"),
    PySlot_STATIC_DATA(Py_mod_doc, "my docstring"),
    PySlot_SIZE(Py_mod_state_size, sizeof(my_state_struct)),
    PySlot_STATIC_DATA(Py_mod_methods, my_methods),
    PySlot_STATIC_DATA(Py_mod_slots, my_slots),
    PySlot_FUNC(Py_mod_state_traverse, my_traverse),
    PySlot_FUNC(Py_mod_state_clear, my_clear),
    PySlot_FUNC(Py_mod_state_free, my_free),
    PySlot_END
};

PyMODEXPORT_FUNC
PyModExport_<modname>(void)
{
    return my_slot_array;
}

هر فیلدی که غایب بوده است را کنار بگذارید (به‌جز Py_mod_abi جدید) و مقادیر خود را جایگزین کنید.

برای جزئیات این API، مستندات PySlot و export hook را ببینید.

همانند نمونه، تابع PyModExport_ شما فقط باید یک اشاره‌گر به داده‌ی ایستا برگرداند. اگر نمی‌توانید از کد اضافی جلوگیری کنید، به موارد احتیاطی در مستندات PyModExport مراجعه کنید.

نکته

هنگام ساخت برای ویندوز با استفاده از ابزار ساخت Setuptools، حذف کردن تابع PyInit_<modname> ممکن است منجر به خطای پیونددهنده‌ی LINK : error LNK2001: unresolved external symbol PyInit_<modname> شود. این مشکل به این دلیل رخ می‌دهد که Setuptools یک پرچم پیونددهنده‌ی /EXPORT را ارسال می‌کند که از پایتون ۳.۱۵ به بعد اضافی است (نگاه کنید به gh-141671). یک راه حل جایگزین این است که یک تابع PyInit_<modname> نمایشی (dummy) به کد خود اضافه کنید. پایتون ۳.۱۵ به بعد هرگز این تابع را در صورت وجود PyModExport_<modname> فراخوانی نخواهد کرد، بنابراین همیشه می‌تواند با خطا مواجه شود:

// Workaround for https://github.com/pypa/distutils/issues/387
PyMODINIT_FUNC
PyInit_<modname>(void)
{
    PyErr_SetString(PyExc_SystemError,
                    "PyInit_* called for module with PyModExport_*");
    return NULL;
}

(این مسئله در نسخه‌ی ۸۴.۰.۰ از Setuptools وجود دارد؛ ممکن است در نسخه‌های جدیدتر رفع شده باشد.)

جایگاه‌های موجود

اگر یک جایگاه (slot) به نام Py_mod_slots دارید، آرایه‌ای را که به آن اشاره می‌کند بررسی کنید. این آرایه باید مشابه نمونه‌ی زیر یک آرایه‌ی PyModuleDef_Slot باشد:

static PyObject *create_module(PyObject *spec, PyModuleDef *def) { ... }
static int my_first_module_exec(PyObject *module) { ... }
static int my_second_module_exec(PyObject *module) { ... }

static PyModuleDef_Slot my_slots[] = {
   {Py_mod_gil, Py_MOD_GIL_NOT_USED},
   {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
   {Py_mod_create, my_module_create},
   {Py_mod_exec, my_first_module_exec},
   {Py_mod_exec, my_second_module_exec},
   {0, NULL}
};

py_mod_create

اگر یک ورودی Py_mod_create دارید، اطمینان حاصل کنید که تابع می‌تواند با مقدار NULL به عنوان آرگومان دوم (به‌جای PyModuleDef که در حال حذف آن هستید) فراخوانی شود. اغلب، از این آرگومان اصلاً استفاده نمی‌شود؛ می‌توانید با تغییر نام آن این موضوع را بررسی کنید:

static PyObject *create_module(PyObject *spec, PyModuleDef *_unused) { ... }

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

چندین py_mod_exec

اگر بیش از یک ورودی Py_mod_exec دارید، آن‌ها را یکپارچه کنید: تابعی جدید ایجاد کنید که بقیه را فراخوانی کند و جایگاه‌های موجود را با آن جایگزین کنید.

static int my_module_exec(PyObject *module) {
   if (my_first_module_exec(module) < 0) return -1;
   if (my_second_module_exec(module) < 0) return -1;
}

static PyModuleDef_Slot my_slots[] = {
   ...
   /* (remove other Py_mod_exec slots) */
   ...
   {Py_mod_exec, my_module_exec},
   {0, NULL}
};

اگر توابع در جاهای دیگر استفاده نمی‌شوند، می‌توانید به جای آن بدنه‌ی آن‌ها را ترکیب کنید.

ادغام آرایه‌های جایگاه (slot)

به صورت اختیاری، هنگامی که سازگاری با نسخه‌ی ۳.۱۴ پایتون را قطع می‌کنید، می‌توانید با انتقال جایگاه‌ها به آرایه‌ی PySlot و تبدیل تعاریف به PySlot_DATA و PySlot_FUNC، کد را پاکسازی کنید:

static PySlot my_slot_array[] = {
    ...
    PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED),
    PySlot_DATA(Py_mod_multiple_interpreters,
         Py_MOD_PER_INTERPRETER_GIL_SUPPORTED)
    PySlot_FUNC(Py_mod_create, my_module_create),
    PySlot_FUNC(Py_mod_exec, my_module_exec),
    PySlot_END
};

اگر این کار را انجام دادید، آرایه‌ی اصلی PyModuleDef_Slot و ورودی Py_mod_slots آن را حذف کنید.

شیء PyModuleDef مرتبط

از آنجا که رابط برنامه‌نویسی (API) جدید از ساختار PyModuleDef استفاده نمی‌کند، هیچ تعریفی به ماژول حاصل مرتبط نخواهد شد. این موضوع رفتار توابع زیر را تغییر می‌دهد:

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

این توابع معمولاً برای دو منظور استفاده می‌شوند:

  1. برای دریافت تعریفی که ماژول با آن ایجاد شده است. این کار دیگر با API جدید امکان‌پذیر نیست. ماژول‌ها دیگر ارجاعی به تعریف نگه نمی‌دارند، بنابراین باید روش دیگری برای انتقال داده‌های مربوطه پیدا کنید.

  1. برای بررسی اینکه آیا یک شیء ماژول داده‌شده «متعلق به شما» است یا خیر. این کاربرد اکنون توسط module tokens پشتیبانی می‌شود -- اشاره‌گرهای کدر که یک ماژول را شناسایی می‌کنند. برای استفاده از یک توکن، یک متغیر ایستا (یا ثابت) منحصر‌به‌فرد را تعریف (یا دوباره استفاده) کنید، برای مثال:

    static char my_token;
    

    و یک اشاره‌گر به آن را در یک درایه (عنصر) جدید به آرایه‌ی PySlot ماژول خود اضافه کنید:

    static PySlot my_slot_array[] = {
       ...
       PySlot_STATIC_DATA(Py_mod_token, &my_token),
       PySlot_END
    }
    

    سپس، از فراخوانی‌های PyModule_GetDef() مانند موارد زیر:

    PyModuleDef *def = PyModule_GetDef(module);
    

    به PyModule_GetToken() (که از یک آرگومان خروجی استفاده می‌کند و ممکن است با یک استثنا با خطا مواجه شود) تغییر دهید:

    void *token;
    if (PyModule_GetToken(module, &token) < 0) {
        /* handle error */
    }
    

    و از فراخوانی‌های PyType_GetModuleByDef() مانند:

    PyObject *module = PyType_GetModuleByDef(type, my_def);
    /* handle error; use module */
    

    به PyType_GetModuleByToken() (که یک ارجاع قوی برمی‌گرداند):

    PyObject *module = PyType_GetModuleByToken(type, my_token);
    /* handle error; use module */
    Py_XDECREF(module);
    

کدر بودن PyObject

ساختارهای PyObject و PyVarObject در abi3t کدر هستند.

دسترسی به اعضای آن‌ها ممنوع است. اگر این کار را انجام می‌دهید، به توابع getter/setter ذکرشده در مستندات آن‌ها سوئیچ کنید:

همچنین، اندازه‌ی ساختارهای PyObject برای کامپایلر ناشناخته است. این اندازه می‌تواند -- و واقعاً -- بین نسخه‌های مختلف سی‌پایتون تغییر می‌کند.

نکته

اگرچه اندازه‌ی آن در طول ران‌تایم در دسترس است (برای مثال به شکل sys.getsizeof(object()) در کد پایتون)، اما باید در برابر وسوسه‌ی محاسبه‌ی آفست‌های اشاره‌گر بر اساس آن مقاومت کنید. چیدمان حافظه‌ی شیء ممکن است در پیاده‌سازی‌های آینده‌ی abi3t تغییر کند.

تعاریف نوع سفارشی

از آنجا که PyObject کدر است، روش سنتی تعریف انواع سفارشی دیگر کار نمی‌کند:

typedef struct {
   PyObject_HEAD  // expands to `PyObject ob_base;` which has unknown size

   int my_data;
} CustomObject;

static PyType_Spec CustomType_spec = {
   ...
   .basicsize = sizeof(CustomObject),
   ...
};

احتمالاً تمام تعاریف کلاس شما، و تمام کدهایی که به داده‌ی کلاس‌های شما دسترسی دارند، باید بازنویسی شوند. این احتمالاً بزرگ‌ترین تغییری است که برای پشتیبانی از abi3t به آن نیاز دارید.

برای هر چنین نوعی، به‌جای تعریف یک struct برای کل نمونه، یکی را فقط با فیلدهای «اضافی» تعریف کنید -- فیلدهایی که مختص کلاس شما هستند، نه ابرکلاس‌های آن:

typedef struct {
   int my_data;
} CustomObjectData;

نام را تغییر دهید. تقریباً تمام کدهایی که از struct استفاده می‌کنند باید تغییر کنند (به‌ویژه اشاره‌گرها به ساختار جدید را نمی‌توان به/از PyObject* تبدیل نوع داد)، و تغییر نام، کاربردها را به عنوان خطاهای کامپایلر برجسته خواهد کرد. (اگر از typeof، کلمه کلیدی auto در ++C، یا روش‌های مشابهی برای جلوگیری از تایپ نام نوع استفاده می‌کنید، این کارکرد نخواهد داشت. بسیار مراقب باشید و اجرای ابزارهایی برای تشخیص رفتار تعریف‌نشده را در نظر بگیرید.)

سپس، برای ایجاد کلاس، از basicsize منفی استفاده کنید تا به‌جای اندازه‌ی کل نمونه، فضای ذخیره‌سازی «اضافی» را مشخص کنید:

static PyType_Spec CustomType_spec = {
   ...
   .basicsize = -sizeof(CustomObjectData), /* note the minus sign */
   ...
};

اگر از Py_tp_members استفاده می‌کنید، پرچم Py_RELATIVE_OFFSET را روی هر عضو تنظیم کنید و offset را نسبت به struct جدید خود مشخص نمایید.

دسترسی به داده‌ی نوع سفارشی

سپس بخش سخت ماجرا فرا می‌رسد: در تمام کدهایی که نیاز به دسترسی به این struct دارند، به یک فراخوانی اضافی PyObject_GetTypeData() برای بازیابی یک اشاره‌گر CustomObjectData * از PyObject * نیاز خواهید داشت:

PyObject *obj = ...;
CustomObjectData *data = PyObject_GetTypeData(obj, cls);

توجه داشته باشید که این فراخوانی نیازمند شیء نوع برای کلاس شما (cls) است.

اگر کلاس شما قابلیت زیرکلاس‌سازی نداشته باشد (یعنی از پرچم Py_TPFLAGS_BASETYPE استفاده نکند)، مقدار cls برابر با Py_TYPE(obj) خواهد بود. در غیر این صورت، از Py_TYPE همراه با PyObject_GetTypeData() استفاده نکنید: ممکن است حافظه‌ی رزرو شده برای یک زیرکلاس نامرتبط را برگرداند! برای مثال، اگر کاربری زیرکلاسی به شکل زیر بسازد:

class Sub(YourCustomClass):
   __slots__ = ('a', 'b')

آنگاه Py_TYPE(obj) برابر با Sub است و حافظه‌ی زیرین ممکن است به شکل زیر باشد:

╭─ PyObject *obj
│              ╭─ اشاره‌گری که می‌خواهید
│              │                    ╭─ PyObject_GetTypeData(obj, Py_TYPE(obj))
▼              ▼                    ▼
┌──────────┬───┬────────────────┬───┬─────────────┬───┬─────────────┐
│ PyObject │...│ CustomTypeData │...│ PyObject *a │...│ PyObject *b │
└──────────┴───┴────────────────┴───┴─────────────┴───┴─────────────┘

(نقاط چین نشان‌دهنده‌ی پدینگ احتمالی هستند. توجه داشته باشید که این چیدمان حافظه تضمین نمی‌شود: نسخه‌های آینده‌ی پایتون ممکن است پدینگ متفاوتی اضافه کنند یا حتی ترتیب ساختارها را جابه‌جا کنند.)

برای دریافت کلاس درست دو روش اصلی وجود دارد:

  • در متدهای نمونه، پیاده‌سازی شما ممکن است از امضای PyCMethod (و بیت METH_METHOD در PyMethodDef.ml_flags) استفاده کند و کلاس را به عنوان آرگومان defining_class دریافت کند.

  • در غیر این صورت، با استفاده از جایگاه Py_tp_token یک توکن ایستا و یکتا به کلاس خود بدهید و از موارد زیر استفاده کنید:

    PyTypeObject cls;
    if (PyType_GetBaseByToken(Py_TYPE(obj), my_tp_token, &cls) < 0) {
        /* handle error */
    }
    CustomObjectData *data = PyObject_GetTypeData(obj, cls);
    

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

از شرط‌های زمان ساخت اجتناب کنید

کد خود را برای یافتن APIهایی بررسی کنید که نسخه‌ی پایتون مورد استفاده برای ساخت افزونه‌تان را مشخص می‌کنند. این مورد دیگر با پایتونی که افزونه‌تان روی آن اجرا می‌شود مطابقت ندارد، بنابراین کدی که از این اطلاعات استفاده می‌کند اغلب نیازمند تغییر است. ماکروهایی که باید بررسی شوند عبارتند از:

  • PY_VERSION_HEX، PY_MAJOR_VERSION، PY_MINOR_VERSION:

    • برای دریافت نسخه‌ی زمان اجرا، از Py_Version استفاده کنید؛

    • برای تعیین این که کدام C API در دسترس است، از Py_TARGET_ABI3T استفاده کنید. این ماکرو روی حداقل نسخه‌ی پشتیبانی‌شده تنظیم می‌شود.

  • Py_GIL_DISABLED: تحت abi3t، این ماکرو همیشه تعریف می‌شود. کدی که با پایتون نخ‌آزاد کار می‌کند باید با GIL فعال نیز کار کند (از آنجا که GIL می‌تواند در زمان اجرا فعال شود) و معمولاً نیز کار می‌کند (مگر اینکه بنا به دلیلی، به بیش از یک وضعیت نخ متصل در یک زمان نیاز داشته باشد).

تغییرات بیشتر در کد

اگر همچنان با خطاهای کامپایلر یا هشدارها مواجه شدید، راهی برای رفع آن‌ها پیدا کنید. متأسفانه، این راهنما محدود است و نمی‌تواند تمام تغییرات ممکن کد را که افزونه‌ها ممکن است نیاز داشته باشند پوشش دهد.

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

این امکان وجود دارد که مشکل شما برای نسخه‌ی فعلی abi3t قابل رفع نباشد. در این صورت، گزارش دادن آن ممکن است به اولویت‌بندی‌اش برای نسخه‌ی بعدی سی‌پایتون کمک کند.

برچسب‌گذاری و توزیع

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

افزونه‌های ساخته‌شده با abi3t باید پسوند زیر را داشته باشند:

  • در ویندوز: .pyd (مانند هر افزونه‌ی دیگری)؛

  • لینوکس، مک‌اواس و سایر سیستم‌هایی که از پسوند .so استفاده می‌کنند: .abi3t.so (نه .cpython-315t.so یا .abi3.so). توجه داشته باشید که هر دو ساخت‌ نخ‌آزاد و غیرنخ‌آزاد افزونه‌های .abi3t.so را بارگذاری خواهند کرد؛

  • سایر سیستم‌ها: با توزیع‌کننده‌ی خود مشورت کنید و احتمالاً این راهنما را به‌روزرسانی کنید.

اگر افزونه را در قالب یک ویل (wheel) توزیع می‌کنید، از برچسب‌های زیر استفاده کنید:

  • برچسب پایتون: cp3XX، که در آن XX حداقل نسخه‌ی پایتونی است که افزونه برای آن ساخته شده است. (برای مثال، cp315 اگر مقدار Py_TARGET_ABI3T را روی 0x30f0000 تنظیم کنید. برای مقادیر بیشتر، به کامپایل کردن برای ABI پایدار مراجعه کنید.)

  • برچسب ABI: abi3.abi3t. این یک مجموعه برچسب فشرده است که پشتیبانی از ساخت‌های غیرنخ‌آزاد و نخ‌آزاد را نشان می‌دهد.

برای مثال، نام پرونده‌ی ویل ممکن است شبیه به این باشد:

myproject-1.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl

همچنین ببینید

Platform Compatibility Tags در متادیتای توزیع بسته‌ی PyPA.

اگر نام پرونده یا برچسب‌ها نادرست هستند، آن‌ها را اصلاح کنید.

آزمون

توجه داشته باشید که وقتی افزونه‌ای سازگار با چندین نسخه‌ی سی‌پایتون می‌سازید، باید همیشه آن را با هر نسخه‌ای که پشتیبانی می‌کند آزمون کنید (برای مثال، ۳.۱۵، ۳.۱۶ و غیره). ABI پایدار فقط سازگاری ABI را تضمین می‌کند؛ ممکن است تغییرات رفتاری نیز وجود داشته باشد -- هم تغییرات عمدی (که توسط PEP 387 پوشش داده شده‌اند) و هم باگ‌ها.

حتماً آزمون‌ها را روی هر دو ساخت نخ‌آزاد و غیرنخ‌آزاد سی‌پایتون اجرا کنید.

اگر با موفقیت انجام شدند، تبریک می‌گوییم! شما یک افزونه‌ی abi3t دارید.