پشتیبانی از زباله‌روبی چرخه‌ای

پشتیبانی پایتون از تشخیص و جمع‌آوری زباله‌هایی که شامل ارجاع‌های چرخه‌ای هستند، نیازمند پشتیبانی از سوی نوع‌های شیء است که «ظرف»هایی برای اشیاء دیگر به شمار می‌روند و ممکن است خود آن اشیاء نیز ظرف باشند. نوع‌هایی که ارجاعی به اشیاء دیگر ذخیره نمی‌کنند، یا تنها ارجاع‌هایی به نوع‌های اتمی (مانند اعداد یا رشته‌ها) ذخیره می‌کنند، نیازی به ارائه هیچ پشتیبانی صریحی برای زباله‌روبی ندارند.

برای ایجاد یک نوع ظرف، فیلد tp_flags شیء نوع باید شامل Py_TPFLAGS_HAVE_GC باشد و پیاده‌سازی هندلر tp_traverse ارائه شود. اگر نمونه‌های نوع تغییرپذیر باشند، پیاده‌سازی tp_clear نیز باید ارائه شود.

Py_TPFLAGS_HAVE_GC

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

سازنده‌های انواع ظرف باید از دو قاعده پیروی کنند:

  1. حافظه برای شیء باید با استفاده از PyObject_GC_New یا PyObject_GC_NewVar تخصیص یابد.

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

به‌طور مشابه، آزادساز حافظه‌ی شیء باید از جفت قاعده‌ی مشابهی پیروی کند:

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

  2. حافظه‌ی شیء باید با استفاده از PyObject_GC_Del() آزاد شود.

    هشدار

    اگر نوعی Py_TPFLAGS_HAVE_GC را اضافه کند، آن‌گاه باید حداقل یک هندلر tp_traverse را پیاده‌سازی کند یا صریحاً از هندلری در زیرکلاس یا زیرکلاس‌های خود استفاده کند.

    هنگام فراخوانی PyType_Ready() یا برخی از API‌هایی که به‌طور غیرمستقیم آن را فراخوانی می‌کنند، مانند PyType_FromSpecWithBases() یا PyType_FromSpec()، اگر نوع از کلاسی ارث‌بری کند که پروتکل زباله‌روب را پیاده‌سازی کرده باشد و کلاس فرزند شامل پرچم Py_TPFLAGS_HAVE_GC نباشد، مفسر به‌طور خودکار فیلدهای tp_flags، tp_traverse و tp_clear را پر می‌کند.

PyObject_GC_New(TYPE, typeobj)

مشابه PyObject_New است، اما برای اشیاء ظرفی است که پرچم Py_TPFLAGS_HAVE_GC روی آن‌ها تنظیم شده است.

برای تخصیص حافظه به یک شیء، این را مستقیماً فراخوانی نکنید؛ در عوض، جایگاه tp_alloc نوع را فراخوانی کنید.

هنگام پر کردن جایگاه tp_alloc یک نوع، PyType_GenericAlloc() به تابع سفارشی‌ای که صرفاً این ماکرو را فراخوانی می‌کند ترجیح داده می‌شود.

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

PyObject_GC_NewVar(TYPE, typeobj, size)

مشابه PyObject_NewVar است، اما برای اشیاء ظرفی که پرچم Py_TPFLAGS_HAVE_GC برایشان تنظیم شده است.

برای تخصیص حافظه به یک شیء، این را مستقیماً فراخوانی نکنید؛ در عوض، جایگاه tp_alloc نوع را فراخوانی کنید.

هنگام پر کردن جایگاه tp_alloc یک نوع، PyType_GenericAlloc() به تابع سفارشی‌ای که صرفاً این ماکرو را فراخوانی می‌کند ترجیح داده می‌شود.

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

PyObject *PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *type, size_t extra_size)
این است API ناپایداراین ممکن است بدون هشدار در نسخه‌های جزئی تغییر کند.

مشابه PyObject_GC_New است، اما extra_size بایت در انتهای شیء تخصیص می‌دهد (در آفست tp_basicsize). حافظه‌ی تخصیص‌یافته با صفر مقداردهی اولیه می‌شود، به‌جز سرآیند شیء پایتون.

داده‌ی اضافی به همراه شیء آزادسازی خواهد شد، اما در غیر این صورت توسط پایتون مدیریت نمی‌شود.

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

هشدار

این تابع به‌عنوان ناپاید علامت‌گذاری شده است زیرا سازوکار نهایی برای رزرو داده‌های اضافی پس از یک نمونه هنوز تصمیم‌گیری نشده است. برای تخصیص تعداد متغیری از فیلدها، بهتر است به‌جای آن از PyVarObject و tp_itemsize استفاده کنید.

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

PyObject_GC_Resize(TYPE, op, newsize)

تغییر اندازه‌ی شیء‌ای که توسط PyObject_NewVar تخصیص‌یافته است. شیء تغییراندازه‌یافته از نوع TYPE* (منظور هر نوع C است) را برمی‌گرداند یا در صورت شکست NULL.

op باید از نوع PyVarObject* باشد و هنوز توسط جمع‌کننده زباله پیگیری نشده باشد. newsize باید از نوع Py_ssize_t باشد.

void PyObject_GC_Track(PyObject *op)
قسمتی از ABI پایدار.

شیء op را به مجموعه‌ی اشیاء ظرفی که توسط زباله‌روب پیگیری می‌شوند اضافه می‌کند. زباله‌روب می‌تواند در زمان‌های غیرمنتظره اجرا شود، بنابراین اشیاء باید در حین پیگیری معتبر باشند. این تابع باید پس از معتبر شدن همه‌ی فیلدهایی که هندلر tp_traverse دنبال می‌کند فراخوانی شود، که معمولاً نزدیک به پایان سازنده انجام می‌شود.

int PyObject_IS_GC(PyObject *obj)

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

اگر این تابع مقدار 0 را برگرداند، شیء توسط زباله‌روب قابل پیگیری نخواهد بود.

int PyObject_GC_IsTracked(PyObject *op)
قسمتی از ABI پایدار از نسخه‌ی 3.9.

اگر نوع شیء op پروتکل GC را پیاده‌سازی کرده باشد و op در حال حاضر توسط زباله‌روب پیگیری شود، ۱ را برمی‌گرداند و در غیر این صورت ۰.

این مشابه تابع پایتون gc.is_tracked() است.

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

int PyObject_GC_IsFinalized(PyObject *op)
قسمتی از ABI پایدار از نسخه‌ی 3.9.

اگر نوع شیء op پروتکل زباله‌روبی را پیاده‌سازی کرده باشد و op قبلاً توسط زباله‌روب نهایی‌سازی شده باشد، مقدار ۱ و در غیر این صورت مقدار ۰ را برمی‌گرداند.

این مشابه تابع پایتونی gc.is_finalized() است.

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

void PyObject_GC_Del(void *op)
قسمتی از ABI پایدار.

حافظه‌ای را که با PyObject_GC_New یا PyObject_GC_NewVar به یک شیء تخصیص یافته است، آزاد می‌کند.

برای آزاد کردن حافظه‌ی یک شیء، این را مستقیماً فراخوانی نکنید؛ در عوض جایگاه tp_free نوع را فراخوانی کنید.

از این برای حافظه‌ی تخصیص‌یافته توسط PyObject_New، PyObject_NewVar یا توابع تخصیص مرتبط استفاده نکنید؛ به جای آن از PyObject_Free() استفاده کنید.

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

void PyObject_GC_UnTrack(void *op)
قسمتی از ABI پایدار.

شیء op را از مجموعه‌ی اشیای ظرفی که توسط جمع‌کننده پیگیری می‌شوند حذف می‌کند. توجه داشته باشید که PyObject_GC_Track() می‌تواند دوباره روی این شیء فراخوانی شود تا آن را به مجموعه‌ی اشیای پیگیری‌شده بازگرداند. آزادساز حافظه‌ی (هندلر tp_dealloc) باید این تابع را برای این شیء پیش از آنکه هر یک از فیلدهای مورد استفاده‌ی هندلر tp_traverse نامعتبر شوند، فراخوانی کند.

تغییر یافته در نسخه‌ی 3.8: ماکروهای _PyObject_GC_TRACK() و _PyObject_GC_UNTRACK() از API عمومی C حذف شده‌اند.

هندلر tp_traverse یک پارامتر تابع از این نوع می‌پذیرد:

typedef int (*visitproc)(PyObject *object, void *arg)
قسمتی از ABI پایدار.

نوع تابع بازدیدکننده (visitor function) که به هندلر tp_traverse پاس داده می‌شود. این تابع باید با یک شیء برای پیمایش به‌عنوان object و پارامتر سوم هندلر tp_traverse به‌عنوان arg فراخوانی شود. هسته‌ی پایتون از چندین تابع بازدیدکننده برای پیاده‌سازی تشخیص زباله‌ی چرخه‌ای استفاده می‌کند؛ انتظار نمی‌رود که کاربران نیازی به نوشتن توابع بازدیدکننده‌ی خودشان داشته باشند.

هندلر tp_clear باید از نوع inquiry باشد، یا در صورتی که شیء تغییرناپذیر است، NULL باشد.

typedef int (*inquiry)(PyObject *self)
قسمتی از ABI پایدار.

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

پیمایش

هندلر tp_traverse باید از نوع زیر باشد:

typedef int (*traverseproc)(PyObject *self, visitproc visit, void *arg)
قسمتی از ABI پایدار.

تابع پیمایش برای یک شیء جمع‌آوری‌شده در زباله‌جمع‌کن، که توسط زباله‌جمع‌کن برای تشخیص چرخه‌های ارجاع استفاده می‌شود. پیاده‌سازی‌ها باید تابع visit را برای هر شیء که مستقیماً توسط self دربرگرفته شده است فراخوانی کنند، به طوری که پارامترهای ارسالی به visit شامل شیء محصورشده و مقدار arg ارسال‌شده به هندلر باشند. تابع visit نباید با یک آرگومان شیء NULL فراخوانی شود. اگر visit یک مقدار غیرصفر برگرداند، آن مقدار باید بلافاصله بازگردانده شود.

یک تابع tp_traverse معمولی، ماکروی کمکی Py_VISIT() را روی هر یک از اعضای نمونه فراخوانی می‌کند که از اشیای پایتون متعلق به نمونه هستند. برای نمونه، این یک تابع پیمایش (کمی قدیمی‌شده) برای کلاس threading.local است:

static int
local_traverse(PyObject *op, visitproc visit, void *arg)
{
    localobject *self = (localobject *) op;
    Py_VISIT(Py_TYPE(self));
    Py_VISIT(self->args);
    Py_VISIT(self->kw);
    Py_VISIT(self->dict);
    return 0;
}

نکته

تابع Py_VISIT() ایجاب می‌کند که پارامترهای visit و arg در تابع local_traverse() دارای این نام‌های مشخص باشند؛ آن‌ها را با هر نام دلخواهی نام‌گذاری نکنید.

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

Py_VISIT(Py_TYPE(self));

به‌عنوان جایگزین، نوع ممکن است با فراخوانی tp_traverse از یک سوپرکلاس تخصیص‌یافته در هیپ (یا یک نوع تخصیص‌یافته‌ی دیگر در هیپ، در صورت لزوم)، این مسئولیت را واگذار کند. اگر این کار را نکنند، ممکن است شیء نوع زباله‌روبی نشود.

اگر بیت Py_TPFLAGS_MANAGED_DICT در فیلد tp_flags تنظیم شده باشد، تابع پیمایش باید تابع PyObject_VisitManagedDict() را به این شکل فراخوانی کند:

int err = PyObject_VisitManagedDict((PyObject*)self, visit, arg);
if (err) {
    return err;
}

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

تابع پیمایش محدودیتی دارد:

هشدار

تابع پیمایش نباید هیچ اثر جانبی داشته باشد. پیاده‌سازی‌ها نباید شماره‌های ارجاع هیچ‌یک از اشیای پایتون را تغییر دهند و همچنین نباید هیچ شیء پایتونی را، به صورت مستقیم یا غیرمستقیم، ایجاد یا نابود کنند.

این یعنی بیشتر توابع C API پایتون ممکن است استفاده نشوند، زیرا می‌توانند یک استثنای جدید ایجاد کنند، یک ارجاع جدید به یک شیء نتیجه را برگردانند، یا منطق داخلی داشته باشند که از عوارض جانبی استفاده می‌کند. همچنین، مگر اینکه طور دیگری مستند شده باشد، توابعی که عوارض جانبی ندارند ممکن است در نسخه‌های آینده بدون هشدار پیدا کردن عوارض جانبی را شروع کنند.

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

نکته

فراخوانی Py_VISIT() را می‌توان برای آن دسته از اعضایی که ثابت شده نمی‌توانند در چرخه‌های ارجاع شرکت کنند، رد کرد. در نمونه‌ی local_traverse در بالا، یک عضو self->key نیز وجود دارد، اما این عضو فقط می‌تواند NULL یا یک رشته‌ی پایتون باشد و بنابراین نمی‌تواند بخشی از یک چرخه ارجاع باشد.

از سوی دیگر، حتی اگر می‌دانید که یک عضو هرگز نمی‌تواند بخشی از یک چرخه باشد، به عنوان یک ابزار کمکی برای اشکال‌زدایی ممکن است بخواهید به هر حال آن را بازدید کنید تا تابع get_referents() در ماژول gc آن را در بر بگیرد.

نکته

تابع tp_traverse را می‌توان از هر نخی فراخوانی کرد.

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

تغییر یافته در نسخه‌ی 3.9: انتظار می‌رود انواع تخصیص‌یافته در هیپ، Py_TYPE(self) را در tp_traverse بازدید کنند. در نسخه‌های قبلی پایتون، به دلیل bug 40217، انجام این کار ممکن است منجر به فروپاشی در زیرکلاس‌ها شود.

برای ساده‌تر کردن نوشتن هندلرهای tp_traverse، یک ماکرو به نام Py_VISIT() فراهم شده است. به منظور استفاده از این ماکرو، پیاده‌سازی tp_traverse باید آرگومان‌های خود را دقیقاً visit و arg نام‌گذاری کند:

Py_VISIT(o)

اگر عبارت PyObject* o برابر با NULL نیست، کال‌بک visit را با آرگومان‌های o و arg فراخوانی کنید. اگر visit یک مقدار غیرصفر برگرداند، آنگاه همان مقدار را برگردانید.

این مورد تقریباً با این معادل است:

#define Py_VISIT(o)                             \
   if (op) {                                    \
      int visit_result = visit(o, arg);         \
      if (visit_result != 0) {                  \
         return visit_result;                   \
      }                                         \
   }

توابع امن برای پیمایش

استفاده از توابع و ماکروهای زیر در یک هندلر tp_traverse امن است:

توابع «DuringGC»

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

این توابع درست مانند همتایان خود بدون پسوند _DuringGC عمل می‌کنند، اما تضمین می‌شود که هیچ عارضه‌ی جانبی نداشته باشند، در صورت بروز خطا هیچ استثنایی را تنظیم نمی‌کنند، و همان‌طور که در مستندات هر کدام به تفصیل آمده است، ارجاع‌های امانتی را برمی‌گردانند/تنظیم می‌کنند.

توجه داشته باشید که این توابع ممکن است با خطا مواجه شوند (مقادیر NULL یا -1 را برگردانند)، اما از آنجا که استثنایی را تنظیم نمی‌کنند، هیچ اطلاعات خطایی در دسترس نیست. در برخی موارد، خطا از نتیجه‌ی موفقیت‌آمیز NULL قابل تشخیص نیست.

void *PyObject_GetTypeData_DuringGC(PyObject *o, PyTypeObject *cls)
void *PyObject_GetItemData_DuringGC(PyObject *o)
void *PyType_GetModuleState_DuringGC(PyTypeObject *type)
void *PyModule_GetState_DuringGC(PyObject *module)
int PyModule_GetToken_DuringGC(PyObject *module, void **result)
قسمتی از ABI پایدار از نسخه‌ی 3.15.

برای کسب اطلاعات عمومی به توابع «DuringGC» مراجعه کنید.

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

int PyType_GetBaseByToken_DuringGC(PyTypeObject *type, void *tp_token, PyTypeObject **result)
قسمتی از ABI پایدار از نسخه‌ی 3.15.

برای کسب اطلاعات عمومی به توابع «DuringGC» مراجعه کنید.

مقدار *result را به‌جای یک ارجاع قوی، روی یک ارجاع امانتی تنظیم می‌کند. این ارجاع تا زمان پایان فراخوانی هندلر tp_traverse معتبر است.

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

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

PyType_GetBaseByToken()

PyObject *PyType_GetModule_DuringGC(PyTypeObject *type)
PyObject *PyType_GetModuleByToken_DuringGC(PyTypeObject *type, const void *mod_token)
مقدار بازگشتی: مرجع امانتی. قسمتی از ABI پایدار از نسخه‌ی 3.15.

برای کسب اطلاعات عمومی به توابع «DuringGC» مراجعه کنید.

این توابع یک ارجاع امانتی برمی‌گردانند که تا زمان پایان فراخوانی هندلر tp_traverse معتبر است.

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

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

PyType_GetModule(), PyType_GetModuleByToken()

کنترل وضعیت زباله‌روب

API زبان C توابع زیر را برای کنترل اجراهای زباله‌روبی فراهم می‌کند.

Py_ssize_t PyGC_Collect(void)
قسمتی از ABI پایدار.

در صورت فعال بودن زباله‌روب، یک زباله‌روبی کامل انجام می‌دهد. (توجه داشته باشید که gc.collect() آن را بدون قید و شرط اجرا می‌کند.)

تعداد اشیاء جمع‌آوری‌شده + اشیاء دسترس‌ناپذیری که نمی‌توان آن‌ها را جمع‌آوری کرد را برمی‌گرداند. اگر زباله‌روب غیرفعال باشد یا از قبل در حال جمع‌آوری باشد، بلافاصله 0 را برمی‌گرداند. خطاهای رخ‌داده در حین زباله‌روبی به sys.unraisablehook منتقل می‌شوند. این تابع استثنایی ایجاد نمی‌کند.

int PyGC_Enable(void)
قسمتی از ABI پایدار از نسخه‌ی 3.10.

فعال کردن زباله‌روب: مشابه gc.enable(). وضعیت قبلی را برمی‌گرداند، ۰ برای غیرفعال و ۱ برای فعال.

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

int PyGC_Disable(void)
قسمتی از ABI پایدار از نسخه‌ی 3.10.

جمع‌کننده زباله را غیرفعال می‌کند: مشابه gc.disable(). وضعیت قبلی را برمی‌گرداند، 0 برای غیرفعال و 1 برای فعال.

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

int PyGC_IsEnabled(void)
قسمتی از ABI پایدار از نسخه‌ی 3.10.

وضعیت زباله‌روب را پرس‌وجو می‌کند: مشابه gc.isenabled(). وضعیت فعلی را برمی‌گرداند، ۰ برای غیرفعال و ۱ برای فعال.

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

پرس‌وجوی وضعیت زباله‌روب

C-API رابط زیر را برای استعلام اطلاعات درباره‌ی زباله‌روب فراهم می‌کند.

void PyUnstable_GC_VisitObjects(gcvisitobjects_t callback, void *arg)
این است API ناپایداراین ممکن است بدون هشدار در نسخه‌های جزئی تغییر کند.

callback داده‌شده را روی تمام اشیای زنده‌ی قابل زباله‌روبی اجرا می‌کند. arg بدون تغییر به تمام فراخوانی‌های callback پاس داده می‌شود.

هشدار

اگر اشیاء جدیدی توسط کال‌بک تخصیص داده (یا آزادسازی) شوند، تعریف‌نشده است که آیا بازدید خواهند شد یا خیر.

زباله‌روبی در حین عملیات غیرفعال است. اجرای صریح یک زباله‌روبی در کال‌بک ممکن است به رفتار تعریف‌نشده منجر شود؛ برای مثال، پیمایش همان اشیاء چندین بار یا اصلاً پیمایش‌نشدن آن‌ها.

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

typedef int (*gcvisitobjects_t)(PyObject *object, void *arg)

نوع تابع بازدیدکننده‌ای که باید به PyUnstable_GC_VisitObjects() پاس داده شود. arg همان argی است که به PyUnstable_GC_VisitObjects پاس داده شده است. برای ادامه‌ی پیمایش 1 را برگردانید و برای توقف پیمایش 0 را برگردانید. سایر مقادیر بازگشتی فعلاً رزرو شده‌اند، بنابراین رفتار در صورت برگرداندن هر مقدار دیگر تعریف‌نشده است.

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