چرخه‌ی حیات شیء

این بخش توضیح می‌دهد که جایگاه‌های یک نوع در طول عمر یک شیء چگونه با یکدیگر در ارتباط هستند. این بخش به‌عنوان مرجع کامل و کانونیکال برای جایگاه‌ها در نظر گرفته نشده است؛ در عوض، برای جزئیات درباره‌ی یک جایگاه خاص، به مستندات اختصاصی جایگاه در ساختارهای شیء نوع مراجعه کنید.

رویدادهای چرخه حیات

شکل زیر ترتیب رویدادهایی را که در طول عمر یک شیء می‌توانند رخ دهند نشان می‌دهد. پیکانی از A به B نشان می‌دهد که رویداد B می‌تواند پس از رخ دادن رویداد A رخ دهد، و برچسب پیکان، شرطی را که برای رخ دادن B پس از A باید برقرار باشد، مشخص می‌کند.

Life Events tp_new tp_new start->tp_new    type call   tp_alloc tp_alloc tp_new->tp_alloc  direct call   tp_init tp_init tp_new->tp_init reachable reachable tp_init->reachable reachable->tp_init tp_traverse tp_traverse reachable->tp_traverse  not in a    cyclic    isolate   reachable->tp_traverse  periodic    cyclic isolate     detection   finalized? marked as finalized? reachable->finalized?  no refs   tp_finalize tp_finalize reachable->tp_finalize  resurrected    (maybe remove    finalized mark)   uncollectable uncollectable (leaked) reachable->uncollectable  cyclic    isolate    (no GC    support)   tp_dealloc tp_dealloc reachable->tp_dealloc  no refs tp_traverse->finalized?  cyclic    isolate   finalized?->tp_finalize  no (mark    as finalized)   tp_clear tp_clear finalized?->tp_clear  yes   tp_finalize->tp_clear  no refs or     cyclic isolate   tp_finalize->tp_dealloc  recommended  call (see  explanation) tp_finalize->tp_dealloc   no refs   tp_clear->uncollectable  cyclic    isolate   tp_clear->tp_dealloc  no refs   tp_free tp_free tp_dealloc->tp_free    direct call  

توضیح:

  • هنگامی که شیء جدیدی با فراخوانی نوع آن ساخته می‌شود:

    1. tp_new برای ایجاد یک شیء جدید فراخوانی می‌شود.

    2. tp_alloc مستقیماً توسط tp_new برای تخصیص حافظه‌ی شیء جدید فراخوانی می‌شود.

    3. tp_init شیء تازه‌ایجادشده را مقداردهی اولیه می‌کند. در صورت تمایل می‌توان tp_init را دوباره فراخوانی کرد تا یک شیء مجدداً مقداردهی اولیه شود. همچنین می‌توان از فراخوانی tp_init به‌طور کامل صرف‌نظر کرد، برای مثال توسط کد پایتونی که __new__() را فراخوانی می‌کند.

  • پس از تکمیل tp_init، شیء آماده استفاده است.

  • مدتی پس از حذف آخرین ارجاع به یک شیء:

    1. اگر شیئی به‌عنوان نهایی‌شده علامت‌گذاری نشده باشد، می‌توان آن را با علامت‌گذاری به‌عنوان نهایی‌شده و فراخوانی تابع tp_finalize آن نهایی‌سازی کرد. پایتون هنگامی که آخرین ارجاع به یک شیء حذف می‌شود، آن را نهایی‌سازی نمی‌کند؛ برای اطمینان از اینکه tp_finalize همیشه فراخوانی می‌شود، از PyObject_CallFinalizerFromDealloc() استفاده کنید.

    2. اگر شیء به‌عنوان نهایی‌سازی‌شده علامت‌گذاری شده باشد، ممکن است tp_clear توسط زباله‌روب فراخوانی شود تا ارجاع‌هایی را که شیء نگه‌داشته است پاک کند. این متد هنگامی که شمارش ارجاع شیء به صفر برسد فراخوانی نمی‌شود.

    3. tp_dealloc برای نابود کردن شیء فراخوانی می‌شود. برای جلوگیری از تکرار کد، tp_dealloc معمولاً tp_clear را فراخوانی می‌کند تا ارجاع‌های شیء را آزاد کند.

    4. وقتی tp_dealloc تخریب شیء را به پایان می‌رساند، برای آزادسازی حافظه tp_free را مستقیماً فراخوانی می‌کند (که معمولاً به‌طور خودکار و متناسب با نوع، روی PyObject_Free() یا PyObject_GC_Del() تنظیم می‌شود).

  • تابع tp_finalize در صورت تمایل مجاز است ارجاعی به شیء اضافه کند. اگر چنین کند، شیء احیا می‌شود و از تخریب در انتظارش جلوگیری می‌شود. (تنها tp_finalize مجاز است یک شیء را احیا کند؛ tp_clear و tp_dealloc بدون فراخوانی tp_finalize نمی‌توانند این کار را انجام دهند.) احیای یک شیء ممکن است باعث حذف نشان نهایی‌شده از روی آن شود یا نشود. در حال حاضر، پایتون اگر شیء احیاشده از زباله‌روبی پشتیبانی کند (یعنی پرچم Py_TPFLAGS_HAVE_GC تنظیم شده باشد)، نشان نهایی‌شده را از روی آن حذف نمی‌کند، اما اگر شیء از زباله‌روبی پشتیبانی نکند، این نشان را حذف می‌کند؛ هر یک یا هر دوی این رفتارها ممکن است در آینده تغییر کنند.

  • tp_dealloc می‌تواند در صورت تمایل به استفاده مجدد از آن کد برای کمک به تخریب شیء، به‌صورت اختیاری tp_finalize را از طریق PyObject_CallFinalizerFromDealloc() فراخوانی کند. انجام این کار توصیه می‌شود، زیرا تضمین می‌کند که tp_finalize همیشه پیش از تخریب فراخوانی می‌شود. برای مشاهده کد نمونه، به مستندات tp_dealloc مراجعه کنید.

  • اگر شیء عضوی از یک cyclic isolate باشد و یا tp_clear در شکستن چرخه ارجاع ناکام بماند یا مجموعه ایزوله چرخه‌ای تشخیص داده نشود (شاید gc.disable() فراخوانی شده باشد، یا پرچم Py_TPFLAGS_HAVE_GC به‌اشتباه در یکی از نوع‌های درگیر تنظیم نشده باشد)، اشیاء به‌طور نامحدود غیرقابل جمع‌آوری باقی می‌مانند (آن‌ها «نشت» می‌کنند). gc.garbage را ببینید.

اگر شیء به‌عنوان پشتیبان از زباله‌روبی نشانه‌گذاری شده باشد (پرچم Py_TPFLAGS_HAVE_GC در tp_flags تنظیم شده باشد)، رویدادهای زیر نیز ممکن‌اند:

  • زباله‌روب گاهی برای شناسایی ایزوله‌های چرخه‌ای، tp_traverse را فراخوانی می‌کند.

  • هنگامی که زباله‌روب یک cyclic isolate را کشف می‌کند، یکی از اشیاء درون گروه را با علامت‌گذاری آن به‌عنوان نهایی‌شده و فراخوانی تابع tp_finalize آن (در صورت وجود) نهایی‌سازی می‌کند. این کار تا زمانی تکرار می‌شود که ایزوله چرخه‌ای دیگر وجود نداشته باشد یا همه‌ی اشیاء نهایی‌سازی شده باشند.

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

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

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

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

  1. قابل دسترس (هنوز ایزوله چرخه‌ای نیست): همه‌ی اشیاء در وضعیت عادی و قابل دسترس خود هستند. ممکن است چرخه‌ی ارجاع وجود داشته باشد، اما وجود یک ارجاع بیرونی به این معناست که اشیاء هنوز ایزوله نشده‌اند.

  2. دسترس‌ناپذیر اما سازگار: آخرین ارجاع از بیرون گروه چرخه‌ای اشیاء حذف شده است و در نتیجه اشیاء ایزوله شده‌اند (بدین ترتیب یک ایزوله چرخه‌ای (cyclic isolate) متولد می‌شود). هیچ‌یک از اشیاء گروه هنوز نهایی‌سازی یا پاک‌سازی نشده‌اند. ایزوله چرخه‌ای در این مرحله باقی می‌ماند تا اجرای آینده‌ای از زباله‌روب انجام شود (که لزوماً اجرای بعدی نیست، زیرا ممکن است اجرای بعدی هر شیء را پویش نکند).

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

  4. همه نهایی‌سازی می‌شوند: همه‌ی اشیاء موجود در یک ایزوله چرخه‌ای (cyclic isolate) پیش از پاک شدن هر یک از آن‌ها نهایی‌سازی می‌شوند.

  5. ترکیبی از نهایی‌سازی‌شده و پاک‌سازی‌شده: اشیاء می‌توانند به‌صورت متوالی یا هم‌زمان پاک‌سازی شوند (اما با در اختیار داشتن قفل مفسر سراسری)؛ در هر دو حالت، برخی پیش از بقیه پایان می‌یابند. یک شیء نهایی‌سازی‌شده باید بتواند پاک‌سازی زیرمجموعه‌ای از اشیایی که به آن‌ها ارجاع دارد را تحمل کند. PEP 442 این مرحله را «زباله چرخه‌ای» (cyclic trash) می‌نامد.

  6. نشت‌شده: اگر یک ایزوله چرخه‌ای پس از نهایی‌سازی و پاک‌سازی همه‌ی اشیاء گروه همچنان وجود داشته باشد، در این صورت اشیاء برای همیشه غیرقابل جمع‌آوری باقی می‌مانند (به gc.garbage مراجعه کنید). اگر یک ایزوله چرخه‌ای به این مرحله برسد، این یک باگ است---یعنی متدهای tp_clear اشیاء شرکت‌کننده نتوانسته‌اند چرخه‌ی ارجاع را همان‌طور که لازم است بشکنند.

اگر tp_clear وجود نداشت، پایتون هیچ راهی نداشت تا یک چرخه‌ی ارجاع را به‌طور ایمن قطع کند. صرفاً نابود کردن یک شیء در یک مجموعه‌ی ایزوله چرخه‌ای منجر به یک اشاره‌گر سرگردان می‌شد و هنگامی که شیئی که به شیء نابودشده ارجاع می‌کند، خود نابود شود، رفتار تعریف‌نشده را برمی‌انگیخت. مرحله‌ی پاک‌سازی، نابود کردن شیء را به فرایندی دو مرحله‌ای تبدیل می‌کند: نخست tp_clear فراخوانی می‌شود تا اشیاء را تا حدی نابود کند که از یکدیگر گره‌گشایی شوند، سپس tp_dealloc فراخوانی می‌شود تا نابودی را کامل کند.

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

گام نهایی‌سازی برای بازپس‌گیری ایمن اشیاء در یک مجموعه چرخه‌ای ایزوله (cyclic isolate) ضروری نیست، اما وجود آن طراحی نوع‌هایی را آسان‌تر می‌کند که هنگام پاک‌سازی اشیاء به شیوه‌ای معقول رفتار کنند. پاک‌سازی یک شیء ممکن است آن را ناگزیر در وضعیتی معیوب و نیمه‌تخریب‌شده باقی بگذارد---ممکن است فراخوانی هر یک از متدهای شیء پاک‌سازی‌شده یا دسترسی به هر یک از ویژگی‌های آن ناایمن باشد. با نهایی‌سازی، تنها اشیاء نهایی‌سازی‌شده ممکن است با اشیاء پاک‌سازی‌شده تعامل داشته باشند؛ برای اشیاء غیرنهایی‌سازی‌شده تضمین می‌شود که تنها با اشیاء پاک‌سازی‌نشده (اما به‌طور بالقوه نهایی‌سازی‌شده) تعامل داشته باشند.

برای جمع‌بندی تعامل‌های ممکن:

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

  • یک شیء نهایی‌شده ممکن است ارجاع‌هایی به اشیاء نهایی‌نشده، نهایی‌شده و پاک‌سازی‌شده داشته باشد یا آن اشیاء ارجاع‌هایی به آن داشته باشند.

  • یک شیء پاک‌سازی‌شده ممکن است ارجاع‌هایی به اشیاء نهایی‌سازی‌شده و پاک‌سازی‌شده داشته باشد یا از آن‌ها ارجاعی به آن وجود داشته باشد، اما هیچ ارجاعی به اشیاء نهایی‌سازی‌نشده یا از آن‌ها به آن وجود نخواهد داشت.

در نبود هرگونه چرخه ارجاع، یک شیء را می‌توان به‌سادگی به محض حذف آخرین ارجاع به آن نابود کرد؛ گام‌های نهایی‌سازی و پاک‌سازی برای بازپس‌گیری ایمن اشیاء استفاده‌نشده ضروری نیستند. با این حال، می‌تواند مفید باشد که tp_finalize و tp_clear به هر جهت پیش از نابودی به‌طور خودکار فراخوانی شوند، زیرا وقتی همه اشیاء همیشه دنباله یکسانی از رویدادها را تجربه کنند، صرف‌نظر از اینکه در یک مجموعه چرخه‌ای ایزوله مشارکت داشته‌اند یا نه، طراحی نوع ساده‌تر می‌شود. در حال حاضر، پایتون tp_finalize و tp_clear را تنها در صورت نیاز برای نابود کردن یک مجموعه چرخه‌ای ایزوله فراخوانی می‌کند؛ این ممکن است در نسخه‌ای آینده تغییر کند.

توابع

برای تخصیص و آزادسازی حافظه، به تخصیص اشیاء روی هیپ مراجعه کنید.

void PyObject_CallFinalizer(PyObject *op)

شیء را مطابق توضیحات داده‌شده در tp_finalize نهایی‌سازی می‌کند. به جای فراخوانی مستقیم tp_finalize، این تابع (یا PyObject_CallFinalizerFromDealloc()) را فراخوانی کنید، زیرا این تابع ممکن است تکرار فراخوانی‌های متعدد tp_finalize را حذف کند. در حال حاضر، تکرار فراخوانی‌ها تنها زمانی حذف می‌شود که نوع از زباله‌روبی پشتیبانی کند (یعنی پرچم Py_TPFLAGS_HAVE_GC تنظیم شده باشد)؛ این موضوع ممکن است در آینده تغییر کند.

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

int PyObject_CallFinalizerFromDealloc(PyObject *op)

همانند PyObject_CallFinalizer() است، اما در نظر گرفته شده که در ابتدای مخرب شیء (tp_dealloc) فراخوانی شود. نباید هیچ ارجاعی به شیء وجود داشته باشد. اگر نهایی‌ساز شیء آن را احیا کند، این تابع مقدار -1 را برمی‌گرداند؛ هیچ تخریب بیشتری نباید رخ دهد. در غیر این صورت، این تابع مقدار 0 را برمی‌گرداند و تخریب می‌تواند به‌طور عادی ادامه یابد.

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

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

tp_dealloc برای کد نمونه.