فرض کنید برای نمونه‌های موقتِ یک آزمایش، برچسبِ بازبینی نگه می‌داریم. دو نمونه می‌توانند شناسهٔ منطقی یکسان داشته باشند، اما دو شیء جدا باشند. هنگام بازسازیِ نمونهٔ دوم، فراداده را دوباره ثبت می‌کنیم و انتظار داریم تا وقتی نمونهٔ تازه زنده است، برچسب هم باقی بماند. این انتظار در WeakKeyDictionary الزاماً درست نیست: برابر بودنِ کلیدِ تازه، هویتِ کلیدی را که عمرِ ورودی به آن بسته شده عوض نمی‌کند. این نوشته با نمونه‌های کاملاً ساختگی، مرزِ این دو مفهوم را قابل مشاهده می‌کند.

۱. فرادادهٔ نمونه را با هویتِ مالک اشتباه نگیریم

دو شیء جدا با ظاهرِ برابر در جایگاه‌های مستقل
تصویر مفهومی: دو شیء جدا با ظاهرِ برابر در جایگاه‌های مستقل؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

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

مدلِ ما برای هر موقعیتِ ورودی، یک نمونهٔ خصوصی می‌سازد. شمارهٔ موقعیت، هویتِ آموزشیِ آن است؛ شمارهٔ گروه فقط برابری را تعیین می‌کند. همهٔ نمونه‌ها عمداً هشِ صفر دارند تا روشن شود هشِ یکسان، به‌تنهایی برابری نمی‌سازد. برچسبِ فراداده عددی بی‌واحد از صفر تا نه است. این عدد نتیجهٔ اندازه‌گیری، نسخهٔ واقعیِ دستگاه یا اعتبارسنجیِ کالیبراسیون نیست؛ صرفاً کمک می‌کند بازنویسیِ مقدار را ببینیم.

تابع observe_registry فقط یک تا سه نمونه، گروه‌های صفر تا دو و حداکثر شش رویداد می‌پذیرد. رویدادها ثبتِ برچسب، حذفِ ورودی و کنارگذاشتنِ مالک هستند. این محدودیت‌ها سیاستِ آموزشیِ خودِ برنامه‌اند؛ محدودیتِ عمومیِ کتابخانه محسوب نمی‌شوند. هیچ کلیدِ دلخواهِ کاربر، چرخهٔ ارجاع یا روشِ جمع‌آوریِ سفارشی وارد آزمایش نمی‌شود.

در درسِ مالکیتِ مرجع در weakref.finalize، نگه‌داشتنِ متدِ وابسته می‌توانست خودِ شیء را زنده نگه دارد. مسئلهٔ حاضر به وابستگیِ متد نمی‌پردازد؛ تفاوتِ برابریِ دو کلید با هویتِ مالکِ ورودی را بررسی می‌کند.

۲. کلیدِ برابر، مقدار را عوض می‌کند؛ مالک همان قبلی است

تغییرِ برچسب بدون جابه‌جاییِ اتصالِ آن به شیء
تصویر مفهومی: تغییرِ برچسب بدون جابه‌جاییِ اتصالِ آن به شیء؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

دو نمونهٔ نخست را در گروهِ یک و نمونهٔ سوم را در گروهِ دو قرار دهید. ابتدا با نمونهٔ صفر، برچسبِ چهار را ثبت می‌کنیم؛ سپس با نمونهٔ یک، برچسبِ نه را می‌نویسیم. نمونه‌های صفر و یک برابرند، بنابراین دو ورودی نداریم. در گزارش، یک ورودی با هویتِ صفر، گروهِ یک و برچسبِ نه دیده می‌شود. مقدار تازه است، اما کلیدِ واقعیِ نگهداری‌شده همان نمونهٔ نخست است.

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

مقایسهٔ دو کنترل ضروری است. اگر گروه‌ها متفاوت باشند، باوجودِ هشِ مشترک دو ورودی داریم. اگر به‌جای نمونهٔ اصلی، نمونهٔ برابرِ دوم را کنار بگذاریم، ورودیِ نمونهٔ اصلی باقی می‌ماند. این دو شاهد مانع می‌شوند نتیجه را به «هر برخوردِ هش» یا «مرگِ هر شیء برابر» نسبت بدهیم. شمارهٔ هویت در خروجی هم آدرسِ حافظه نیست؛ فقط اندیسِ ثابتِ نمونه در همین آزمایش است.

۳. حذفِ صریح پیش از ثبت، عمرِ ورودی را دوباره تعیین می‌کند

جداکردنِ اتصالِ قدیمی و ساختِ اتصال به شیءِ دوم
تصویر مفهومی: جداکردنِ اتصالِ قدیمی و ساختِ اتصال به شیءِ دوم؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

اگر سیاستِ برنامه این باشد که نمونهٔ تازه صاحبِ ورودی شود، می‌توان ابتدا ورودیِ برابر را صریحاً حذف کرد و سپس با نمونهٔ انتخاب‌شده نوشت. در شاهدِ دوم، پس از ثبتِ اولیه با نمونهٔ صفر، از طریق نمونهٔ یک حذف می‌کنیم و بعد برچسبِ نه را با همان نمونهٔ یک ثبت می‌کنیم. این بار کنارگذاشتنِ نمونهٔ صفر ورودی را حذف نمی‌کند؛ کنارگذاشتنِ نمونهٔ یک آن را حذف می‌کند.

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

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

درسِ عمرِ بافر و memoryview نیز اهمیتِ مالکِ زنده را نشان می‌داد، اما محدودیتِ تغییرِ اندازه و آزادکردنِ نماها داشت. اینجا محدودیتِ بافر یا snapshotِ داده نداریم؛ مرزِ مسئله، عضویتِ نگاشت و کلیدِ نماینده است.

۴. ردگیریِ محدود و مرجعِ مستقلِ بازه‌های عمر

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

پیش از ساختنِ هر نمونه، تمامِ توالی اعتبارسنجی می‌شود. نوع‌ها باید دقیق باشند؛ مثلاً بولین به‌جای عدد و مولد به‌جای تاپل پذیرفته نمی‌شود. هیچ رویدادی نمی‌تواند به نمونه‌ای اشاره کند که قبلاً کنار گذاشته شده است. ردکردنِ یک رویدادِ نامعتبر در انتهای بسته، نباید باعث ساختنِ نمونه‌های ابتدای آن شود. این ویژگیِ رابطِ نوشته‌شدهٔ ماست و دربارهٔ رفتارِ هر نگاشتِ پایتون ادعایی ندارد.

مسیرِ اصلی واقعاً از نگاشتِ استاندارد و مرجع‌های ضعیف استفاده می‌کند. مرجعِ مستقل، برای هر پیشوندِ رویدادها بازه‌های عمر می‌سازد: نخستین ثبت، نماینده را تعیین می‌کند؛ نخستین حذفِ برابر یا کنارگذاشتنِ همان نماینده، بازه را می‌بندد. آخرین ثبتِ داخلِ بازه، برچسب را تعیین می‌کند. در این مسیر نه نگاشتِ ضعیف وجود دارد، نه بازخوانِ جمع‌آوری و نه به‌روزرسانیِ همان جدولِ مسیرِ اصلی.

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

"""Controlled CPython WeakKeyDictionary identity/lifetime experiment.

Values are unitless review tags. This is not a memory or speed benchmark.
"""

import gc
import itertools
import json
import platform
import sys
import weakref


class _FixtureKey:
    __slots__ = ("group", "serial", "__weakref__")

    def __init__(self, group, serial):
        self.group = group
        self.serial = serial

    def __hash__(self):
        return 0

    def __eq__(self, other):
        return type(other) is _FixtureKey and self.group == other.group


def _validate(groups, events):
    if type(groups) is not tuple or type(events) is not tuple:
        raise TypeError("groups and events must be exact tuples")
    if not 1 <= len(groups) <= 3 or len(events) > 6:
        raise ValueError("fixture size or event count exceeds the contract")
    for label in groups:
        if type(label) is not int:
            raise TypeError("group labels must be exact integers")
        if not 0 <= label <= 2:
            raise ValueError("group label outside 0..2")
    available = [True] * len(groups)
    for event in events:
        if type(event) is not tuple or len(event) != 3:
            raise TypeError("every event must be an exact three-tuple")
        action, index, value = event
        if type(action) is not str or type(index) is not int:
            raise TypeError("action/index types are not exact")
        if action not in ("put", "erase", "drop"):
            raise ValueError("unknown action")
        if not 0 <= index < len(groups):
            raise ValueError("identity index outside fixture")
        if action == "put":
            if type(value) is not int:
                raise TypeError("review tag must be an exact integer")
            if not 0 <= value <= 9:
                raise ValueError("review tag outside 0..9")
        elif value is not None:
            raise ValueError("erase/drop must carry None")
        if not available[index]:
            raise ValueError("an event addresses an already dropped identity")
        if action == "drop":
            available[index] = False


def _snapshot(table, probes):
    # This frame and its temporary strong key references end before any drop.
    alive = tuple(probe() is not None for probe in probes)
    entries = tuple(sorted((key.serial, key.group, tag)
                           for key, tag in table.items()))
    return alive, entries


def observe_registry(groups, events):
    """Return initial/after-event immutable observations for controlled keys.

    Entire sequential input validation precedes all fixture/map construction.
    Explicit collection points are observations on this CPython fixture only.
    """
    _validate(groups, events)
    before = gc.isenabled(), gc.get_debug(), gc.get_threshold()
    owners = [_FixtureKey(group, index) for index, group in enumerate(groups)]
    probes = tuple(weakref.ref(key) for key in owners)
    table = weakref.WeakKeyDictionary()
    try:
        snapshots = [_snapshot(table, probes)]
        for action, index, value in events:
            if action == "put":
                table[owners[index]] = value
            elif action == "erase":
                table.pop(owners[index], None)
            else:
                owners[index] = None
            gc.collect(0)
            snapshots.append(_snapshot(table, probes))
        return tuple(snapshots)
    finally:
        table.clear()
        owners.clear()
        gc.collect(0)
        if any(probe() is not None for probe in probes):
            raise AssertionError("an owned fixture key escaped cleanup")
        if before != (gc.isenabled(), gc.get_debug(), gc.get_threshold()):
            raise AssertionError("GC configuration changed")


def history_reference(groups, events):
    """Independent valid-input oracle: epochs from whole event-history intervals.

    No weak references, map updates, GC, or production validator are used.
    The first put starts an ownership interval. An equal erase or that original
    identity's drop ends it; later equal puts change only the interval's tag.
    """
    reports = []
    for stop in range(len(events) + 1):
        prefix = events[:stop]
        alive = tuple(not any(action == "drop" and index == identity
                              for action, index, _ in prefix)
                      for identity in range(len(groups)))
        entries = []
        for group in range(3):
            start_after = -1
            while True:
                starts = [pos for pos, (action, index, _) in enumerate(prefix)
                          if pos > start_after and action == "put"
                          and groups[index] == group]
                if not starts:
                    break
                start = starts[0]
                original = prefix[start][1]
                ends = [pos for pos, (action, index, _) in enumerate(prefix)
                        if pos > start and
                        ((action == "erase" and groups[index] == group)
                         or (action == "drop" and index == original))]
                end = ends[0] if ends else stop
                if end == stop:
                    tags = [value for pos, (action, index, value)
                            in enumerate(prefix)
                            if start <= pos < end and action == "put"
                            and groups[index] == group]
                    entries.append((original, group, tags[-1]))
                    break
                start_after = end
        reports.append((alive, tuple(sorted(entries))))
    return tuple(reports)


def _must_raise(kind, groups, events):
    try:
        observe_registry(groups, events)
    except kind:
        return
    raise AssertionError("expected input rejection")


def _valid_word_without_validator(size, word):
    # Family generation only: each identity may be dropped at most once and
    # cannot subsequently be addressed. It does not predict registry contents.
    for position, (_, index, _) in enumerate(word):
        if not 0 <= index < size:
            return False
        if any(a == "drop" and i == index for a, i, _ in word[:position]):
            return False
    return True


def _family():
    profiles = 0
    observations = 0
    for size in (1, 2, 3):
        alphabet = tuple(event for identity in range(size)
                         for event in (("put", identity, 0),
                                       ("put", identity, 9),
                                       ("erase", identity, None),
                                       ("drop", identity, None)))
        max_length = 3 if size <= 2 else 2
        for groups in itertools.product((0, 1), repeat=size):
            for length in range(max_length + 1):
                for word in itertools.product(alphabet, repeat=length):
                    if not _valid_word_without_validator(size, word):
                        continue
                    actual = observe_registry(groups, word)
                    expected = history_reference(groups, word)
                    assert actual == expected, (groups, word, actual, expected)
                    profiles += 1
                    observations += len(actual)
    return profiles, observations


def _observation_retention_witness():
    # A saved weak key reference does not own a key. A list of actual keys does.
    owners = [_FixtureKey(1, 0), _FixtureKey(1, 1)]
    probes = tuple(weakref.ref(key) for key in owners)
    table = weakref.WeakKeyDictionary()
    table[owners[0]] = 4
    table[owners[1]] = 9
    weak_observations = table.keyrefs()
    retained_keys = list(table.keys())
    try:
        owners[0] = None
        gc.collect(0)
        held = _snapshot(table, probes)
        assert held == ((True, True), ((0, 1, 9),))
        retained_keys.clear()
        gc.collect(0)
        released = _snapshot(table, probes)
        assert released == ((False, True), ())
        assert len(weak_observations) == 1 and weak_observations[0]() is None
        return {"while_key_list_retained": held,
                "after_key_list_released": released,
                "saved_weak_key_reference_is_dead": True}
    finally:
        retained_keys.clear()
        table.clear()
        owners.clear()
        gc.collect(0)
        assert all(probe() is None for probe in probes)


def self_check():
    checks = {}
    before = gc.isenabled(), gc.get_debug(), gc.get_threshold()

    equal = observe_registry((1, 1, 2),
                             (("put", 0, 4), ("put", 1, 9), ("drop", 0, None)))
    assert equal[2] == ((True, True, True), ((0, 1, 9),))
    assert equal[3] == ((False, True, True), ())
    checks["equal_update_keeps_first_actual_key_lifetime"] = True

    transferred = observe_registry((1, 1),
                                   (("put", 0, 4), ("erase", 1, None),
                                    ("put", 1, 9), ("drop", 0, None),
                                    ("drop", 1, None)))
    assert transferred[4] == ((False, True), ((1, 1, 9),))
    assert transferred[5] == ((False, False), ())
    checks["explicit_erase_then_put_selects_new_representative"] = True

    different = observe_registry((0, 2), (("put", 0, 4), ("put", 1, 9)))
    assert different[-1] == ((True, True), ((0, 0, 4), (1, 2, 9)))
    checks["same_hash_does_not_make_unequal_groups_one_key"] = True

    later = observe_registry((1, 1), (("put", 0, 4), ("put", 1, 9),
                                     ("drop", 1, None)))
    assert later[-1] == ((True, False), ((0, 1, 9),))
    checks["dropping_nonrepresentative_equal_identity_keeps_entry"] = True

    erased = observe_registry((1, 1), (("put", 0, 4), ("erase", 1, None)))
    assert erased[-1] == ((True, True), ())
    checks["erase_through_equal_identity_removes_original_entry"] = True

    restarted = observe_registry((1, 1), (("put", 0, 4), ("drop", 0, None),
                                         ("put", 1, 9)))
    assert restarted[-1] == ((False, True), ((1, 1, 9),))
    checks["put_after_original_death_starts_fresh_interval"] = True

    assert observe_registry((2,), ()) == (((True,), ()),)
    assert observe_registry((2,), (("drop", 0, None),))[-1] == ((False,), ())
    assert observe_registry((2,), (("erase", 0, None),))[-1] == ((True,), ())
    checks["initial_empty_unregistered_drop_and_absent_erase"] = True

    global _FixtureKey
    original_class = _FixtureKey
    constructed = []

    def forbidden_fixture(*args):
        constructed.append(args)
        raise AssertionError("late validation allocated a fixture")

    try:
        _FixtureKey = forbidden_fixture
        _must_raise(ValueError, (1, 1), (("put", 0, 4), ("drop", 0, None),
                                         ("put", 0, 9)))
        _must_raise(ValueError, (1,), (("put", 0, 4), ("put", 0, 10)))
        assert constructed == []
    finally:
        _FixtureKey = original_class
    checks["whole_late_invalid_batch_precedes_fixture_construction"] = True

    class IntChild(int):
        pass

    class TupleChild(tuple):
        pass

    class StrChild(str):
        pass

    bad = [
        (TypeError, [1], ()), (TypeError, (True,), ()),
        (TypeError, (IntChild(1),), ()),
        (TypeError, TupleChild((1,)), ()),
        (TypeError, (1,), []),
        (TypeError, (1,), (["put", 0, 4],)),
        (TypeError, (1,), (("put", 0),)),
        (TypeError, (1,), ((StrChild("put"), 0, 4),)),
        (TypeError, (1,), (("put", True, 4),)),
        (TypeError, (1,), (("put", 0, True),)),
        (TypeError, (1,), (("put", 0, IntChild(4)),)),
        (ValueError, (1,), (("PUT", 0, 4),)),
        (ValueError, (1,), (("erase", 0, 0),)),
        (ValueError, (1,), (("drop", 0, False),)),
        (ValueError, (1,), (("drop", 0, None), ("drop", 0, None))),
        (ValueError, (1,), (("drop", 0, None), ("erase", 0, None))),
    ]
    for kind, groups, events in bad:
        _must_raise(kind, groups, events)
    consumed = []

    def lazy():
        consumed.append(True)
        yield ("put", 0, 4)

    iterator = lazy()
    _must_raise(TypeError, (1,), iterator)
    assert consumed == []
    iterator.close()
    checks["exact_types_shapes_action_names_and_no_generator_consumption"] = True

    bounds = [
        ((), ()), ((0, 1, 2, 0), ()), ((-1,), ()), ((3,), ()),
        ((1,), (("put", -1, 0),)), ((1,), (("put", 1, 0),)),
        ((1,), (("put", 0, -1),)), ((1,), (("put", 0, 10),)),
        ((1,), (("erase", 0, None),) * 7),
    ]
    for groups, events in bounds:
        _must_raise(ValueError, groups, events)
    assert len(observe_registry((0, 1, 2), (("put", 2, 9),) * 6)) == 7
    checks["declared_resource_and_numeric_bounds_are_inclusive"] = True

    family_profiles, family_observations = _family()
    assert family_profiles > 0
    checks["finite_history_interval_oracle_family_one_group"] = True

    retention = _observation_retention_witness()
    checks["weak_keyrefs_versus_strong_key_list_observer_effect"] = True

    saved = observe_registry((1, 1), (("put", 1, 4),))
    assert type(saved) is tuple and all(type(item) is tuple for item in saved)
    assert all(type(alive) is tuple and type(entries) is tuple
               for alive, entries in saved)
    assert observe_registry((1, 1), (("put", 0, 9),))[-1][1] == ((0, 1, 9),)
    assert saved == (((True, True), ()), ((True, True), ((1, 1, 4),)))
    checks["detached_immutable_observations_and_independent_calls"] = True

    assert before == (gc.isenabled(), gc.get_debug(), gc.get_threshold())
    checks["owned_fixture_cleanup_and_gc_configuration_preserved"] = True
    assert len(checks) == 14 and all(type(value) is bool and value
                                     for value in checks.values())
    return checks, family_profiles, family_observations, len(bad) + len(bounds) + 3, retention


def run_example():
    checks, profiles, observations, invalids, retention = self_check()
    return {
        "runtime": {"implementation": platform.python_implementation(),
                    "python": platform.python_version()},
        "contract": {"group_labels": [0, 2], "identities": [1, 3],
                     "events": [0, 6], "review_tag_unit": "unitless",
                     "review_tags": [0, 9], "all_fixture_hashes": 0,
                     "collection_generation": 0,
                     "no_memory_or_speed_measurement": True},
        "equal_update_then_drop_original": observe_registry(
            (1, 1, 2), (("put", 0, 4), ("put", 1, 9), ("drop", 0, None))),
        "explicit_erase_and_reinsert": observe_registry(
            (1, 1), (("put", 0, 4), ("erase", 1, None), ("put", 1, 9),
                     ("drop", 0, None), ("drop", 1, None))),
        "observer_retention_witness": retention,
        "finite_family_ONE_group": {"profiles": profiles,
                          "initial_and_after_event_observations": observations,
                          "labels": [0, 1],
                          "word_max_length_for_one_or_two_identities": 3,
                          "word_max_length_for_three_identities": 2,
                          "counts_as_check_groups": 1},
        "invalid_profiles_checked": invalids,
        "groups_total": len(checks),
        "checks": checks,
        "passed": True,
    }


if __name__ == "__main__":
    if platform.python_implementation() != "CPython" or sys.version_info[:3] != (3, 14, 5):
        raise RuntimeError("this recorded observation profile requires CPython 3.14.5")
    sys.stdout.reconfigure(encoding="utf-8", newline="\n")
    print(json.dumps(run_example(), ensure_ascii=True, indent=2))

خروجیِ زیر، تمامِ JSON چاپ‌شدهٔ اجرای ثبت‌شده است. برچسب‌ها عمداً ASCII هستند. نقاطِ صریحِ جمع‌آوری در نسلِ صفر و پاک‌سازیِ نهایی، مشاهداتِ همین نمونه‌های بی‌چرخه در CPython هستند؛ زمان‌بندیِ قابل‌حمل برای آزادسازی تضمین نمی‌کنند. برنامه تنظیمِ فعال‌بودن، آستانه‌ها و حالتِ اشکال‌زداییِ جمع‌آورنده را تغییر نمی‌دهد.

{
  "runtime": {
    "implementation": "CPython",
    "python": "3.14.5"
  },
  "contract": {
    "group_labels": [
      0,
      2
    ],
    "identities": [
      1,
      3
    ],
    "events": [
      0,
      6
    ],
    "review_tag_unit": "unitless",
    "review_tags": [
      0,
      9
    ],
    "all_fixture_hashes": 0,
    "collection_generation": 0,
    "no_memory_or_speed_measurement": true
  },
  "equal_update_then_drop_original": [
    [
      [
        true,
        true,
        true
      ],
      []
    ],
    [
      [
        true,
        true,
        true
      ],
      [
        [
          0,
          1,
          4
        ]
      ]
    ],
    [
      [
        true,
        true,
        true
      ],
      [
        [
          0,
          1,
          9
        ]
      ]
    ],
    [
      [
        false,
        true,
        true
      ],
      []
    ]
  ],
  "explicit_erase_and_reinsert": [
    [
      [
        true,
        true
      ],
      []
    ],
    [
      [
        true,
        true
      ],
      [
        [
          0,
          1,
          4
        ]
      ]
    ],
    [
      [
        true,
        true
      ],
      []
    ],
    [
      [
        true,
        true
      ],
      [
        [
          1,
          1,
          9
        ]
      ]
    ],
    [
      [
        false,
        true
      ],
      [
        [
          1,
          1,
          9
        ]
      ]
    ],
    [
      [
        false,
        false
      ],
      []
    ]
  ],
  "observer_retention_witness": {
    "while_key_list_retained": [
      [
        true,
        true
      ],
      [
        [
          0,
          1,
          9
        ]
      ]
    ],
    "after_key_list_released": [
      [
        false,
        true
      ],
      []
    ],
    "saved_weak_key_reference_is_dead": true
  },
  "finite_family_ONE_group": {
    "profiles": 2966,
    "initial_and_after_event_observations": 10246,
    "labels": [
      0,
      1
    ],
    "word_max_length_for_one_or_two_identities": 3,
    "word_max_length_for_three_identities": 2,
    "counts_as_check_groups": 1
  },
  "invalid_profiles_checked": 28,
  "groups_total": 14,
  "checks": {
    "equal_update_keeps_first_actual_key_lifetime": true,
    "explicit_erase_then_put_selects_new_representative": true,
    "same_hash_does_not_make_unequal_groups_one_key": true,
    "dropping_nonrepresentative_equal_identity_keeps_entry": true,
    "erase_through_equal_identity_removes_original_entry": true,
    "put_after_original_death_starts_fresh_interval": true,
    "initial_empty_unregistered_drop_and_absent_erase": true,
    "whole_late_invalid_batch_precedes_fixture_construction": true,
    "exact_types_shapes_action_names_and_no_generator_consumption": true,
    "declared_resource_and_numeric_bounds_are_inclusive": true,
    "finite_history_interval_oracle_family_one_group": true,
    "weak_keyrefs_versus_strong_key_list_observer_effect": true,
    "detached_immutable_observations_and_independent_calls": true,
    "owned_fixture_cleanup_and_gc_configuration_preserved": true
  },
  "passed": true
}

۵. چهار پرسش دربارهٔ مشاهده، جمع‌آوری و مرزِ ادعا

قابِ مشاهدهٔ سبک در کنار نگهدارندهٔ قویِ نمونه
تصویر مفهومی: قابِ مشاهدهٔ سبک در کنار نگهدارندهٔ قویِ نمونه؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

پرسش یک: چرا زنده‌ماندنِ نمونهٔ برابر، برچسب را نگه نداشت؟ چون ثبتِ دوم فقط مقدار را بازنویسی کرد. وابستگیِ عمر هنوز به شیءِ نمونهٔ نخست بود. در شاهدِ سه‌نمونه‌ای، پس از دو ثبت، نماینده صفر و برچسب نه است؛ با کنارگذاشتنِ صفر، ورودی حذف می‌شود و زنده‌بودنِ نمونهٔ یک این وابستگی را جابه‌جا نمی‌کند.

پرسش دو: آیا هشِ یکسان برای یکی‌شدنِ ورودی‌ها کافی است؟ خیر. در این کلاس همهٔ هش‌ها صفرند، اما گروه‌های متفاوت برابر نیستند و جدا ثبت می‌شوند. برابری، هویت و هش سه مفهومِ مرتبط ولی متمایزند. تغییرندادنِ این ویژگی‌ها در طولِ عمرِ نمونه هم جزو فرض‌های آزمایش است.

پرسش سه: چگونه نمونهٔ تازه را نماینده کنیم؟ با حذفِ صریحِ ورودیِ برابر و ثبتِ دوباره با شیء موردنظر. شاهدِ مربوط، باقی‌ماندنِ ورودی پس از کنارگذاشتنِ نمونهٔ قدیمی و حذف‌شدنش پس از کنارگذاشتنِ نمونهٔ تازه را نشان می‌دهد. این انتقال باید با سیاستِ مالکیتِ برنامه سازگار باشد؛ چندریسمانی‌بودن و هم‌زمانی در این مدل بررسی نشده‌اند.

پرسش چهار: آیا ناپدیدشدنِ ورودی یعنی حافظه به سیستم‌عامل برگشته است؟ خیر. خروجی فقط دسترسی‌پذیریِ نمونه و عضویتِ نگاشت را مشاهده می‌کند. اندازهٔ RSS، سرعت، رفتارِ چرخه‌ها، قلاب‌های دلخواه و آزادسازیِ منابعِ خارجی سنجیده نشده‌اند. نسخهٔ مستنداتِ خوانده‌شده ۳.۱۴.۹، برچسبِ منبعِ کلاس v3.14.5 و محیطِ اجرای ثبت‌شده CPython 3.14.5 است. دو مرجع از یک تولیدکننده‌اند و تأییدِ مستقلِ بیرونی نیستند. تاریخِ دسترسی ۲۲ اکتبر ۲۰۲۶ است؛ تاریخِ نخستین انتشارِ این صفحه‌ها معلوم نیست. پیوندهای داخلی از موجودیِ ذخیره‌شدهٔ ۲۱ اکتبر، ساعتِ ۱۶:۳۸ تهران انتخاب شده‌اند و دسترسیِ زندهٔ امروزِ آن‌ها تأیید نشده است.

منابع و مطالعهٔ بیشتر

بررسی منابع: ۳۰ مهر ۱۴۰۵. تحلیل‌ها و پیشنهادهای اجرایی، جمع‌بندی نویسنده‌اند.