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

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

برای ایجاد یک نوع ظرف، فیلد 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() را روی یک ارجاع فراخوانی نکنید). زباله‌روب در صورتی که تشخیص دهد این شیء در یک چرخه ارجاع درگیر است، این متد را فراخوانی خواهد کرد.

Traversal

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

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

Traversal function for a garbage-collected object, used by the garbage collector to detect reference cycles. Implementations must call the visit function for each object directly contained by self, with the parameters to visit being the contained object and the arg value passed to the handler. The visit function must not be called with a NULL object argument. If visit returns a non-zero value, that value should be returned immediately.

A typical tp_traverse function calls the Py_VISIT() convenience macro on each of the instance's members that are Python objects that the instance owns. For example, this is a (slightly outdated) traversal function for the threading.local class:

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() requires the visit and arg parameters to local_traverse() to have these specific names; don't name them just anything.

Instances of heap-allocated types hold a reference to their type. Their traversal function must therefore visit the type:

Py_VISIT(Py_TYPE(self));

Alternately, the type may delegate this responsibility by calling tp_traverse of a heap-allocated superclass (or another heap-allocated type, if applicable). If they do not, the type object may not be garbage-collected.

If the Py_TPFLAGS_MANAGED_DICT bit is set in the tp_flags field, the traverse function must call PyObject_VisitManagedDict() like this:

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

Only the members that the instance owns (by having strong references to them) must be visited. For instance, if an object supports weak references via the tp_weaklist slot, the pointer supporting the linked list (what tp_weaklist points to) must not be visited as the instance does not directly own the weak references to itself.

The traversal function has a limitation:

هشدار

The traversal function must not have any side effects. Implementations may not modify the reference counts of any Python objects nor create or destroy any Python objects, directly or indirectly.

This means that most Python C API functions may not be used, since they can raise a new exception, return a new reference to a result object, have internal logic that uses side effects. Also, unless documented otherwise, functions that happen to not have side effects may start having them in future versions, without warning.

For a list of safe functions, see a separate section below.

توجه

The Py_VISIT() call may be skipped for those members that provably cannot participate in reference cycles. In the local_traverse example above, there is also a self->key member, but it can only be NULL or a Python string and therefore cannot be part of a reference cycle.

On the other hand, even if you know a member can never be part of a cycle, as a debugging aid you may want to visit it anyway just so the gc module's get_referents() function will include it.

توجه

The tp_traverse function can be called from any thread.

جزئیات پیاده‌سازی در CPython: Garbage collection is a "stop-the-world" operation: even in free threading builds, only one thread state is attached when tp_traverse handlers run.

تغییر یافته در نسخه‌ی 3.9: Heap-allocated types are expected to visit Py_TYPE(self) in tp_traverse. In earlier versions of Python, due to bug 40217, doing this may lead to crashes in subclasses.

To simplify writing tp_traverse handlers, a Py_VISIT() macro is provided. In order to use this macro, the tp_traverse implementation must name its arguments exactly visit and arg:

Py_VISIT(o)

If the PyObject* o is not NULL, call the visit callback, with arguments o and arg. If visit returns a non-zero value, then return it.

This corresponds roughly to:

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

Traversal-safe functions

The following functions and macros are safe to use in a tp_traverse handler:

"DuringGC" functions

The following functions should only be used in a tp_traverse handler; calling them in other contexts may have unintended consequences.

These functions act like their counterparts without the _DuringGC suffix, but they are guaranteed to not have side effects, they do not set an exception on failure, and they return/set borrowed references as detailed in the individual documentation.

Note that these functions may fail (return NULL or -1), but as they do not set an exception, no error information is available. In some cases, failure is not distinguishable from a successful NULL result.

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.

See "DuringGC" functions for common information.

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

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

See "DuringGC" functions for common information.

Sets *result to a borrowed reference rather than a strong one. The reference is valid for the duration of the tp_traverse handler call.

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

همچنین ملاحظه نمائید

PyType_GetBaseByToken()

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

See "DuringGC" functions for common information.

These functions return a borrowed reference, which is valid for the duration of the tp_traverse handler call.

اضافه شده در نسخه‌ی 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.