ویژگی تصویر

کتابخانه weakref در پایتون: مروری جامع

  /  پایتون   /  کتابخانه weakref در پایتون
بنر تبلیغاتی الف

کتابخانهٔ استاندارد weakref در پایتون ابزارهایی برای نگهداری ارجاعات ضعیف (weak references) فراهم می‌کند؛ یعنی ارجاعاتی که جلوی آزادسازی شی را توسط جمع‌آورندهٔ زباله (garbage collector) نمی‌گیرند. در این مقاله به صورت عملی و دقیق، مفاهیم، انواع ساختارها، مثال‌های کاربردی و نکات پیشرفته دربارهٔ weakref را بررسی می‌کنیم.

مفاهیم پایه

ارجاع معمولی (strong reference) در پایتون شمارش ارجاع شی را افزایش می‌دهد و تا زمانی که وجود دارد شی از حافظه پاک نمی‌شود. اما ارجاع ضعیف شمارش ارجاع را افزایش نمی‌دهد؛ وقتی تمام ارجاعات قوی به شی تمام شوند، شی پاک شده و ارجاع ضعیف به یک مقدار “نفی شده” می‌رود (برای ref مقدار None برمی‌گردد یا callback اجرا می‌شود).

  • use-case معمول: کش (caching) اشیاء با امکان آزادسازی خودکار در صورت نیاز حافظه
  • مناسب برای کاهش لوپ‌های مرجع (reference cycles) زمانی که یک ساختار نیاز به نگهداری اشاره به اشیاء دارد

weakref.ref و callback

import weakref

class MyObject:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f"MyObject({self.name})"

obj = MyObject("alpha")
def on_collect(wr):
    print("Object collected:", wr)

w = weakref.ref(obj, on_collect)
print("before:", w())  # MyObject(alpha)

del obj
# پس از پاکسازی، callback اجرا می‌شود و w() برابر None خواهد شد

در این مثال یک ارجاع ضعیف ساخته‌ایم و callback هنگام جمع‌آوری شی اجرا می‌شود. تابع callback یک آرگومان می‌گیرد که خود شی ضعیف‌شده را نشان می‌دهد (که اکنون به یک مرجع قابل خواندن نیست).

WeakValueDictionary (کش بر اساس مقدار)

import weakref

class Data:
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        return f"Data({self.value})"

cache = weakref.WeakValueDictionary()

def add_to_cache(key, value):
    cache[key] = value

d = Data(10)
add_to_cache("ten", d)
print(cache.get("ten"))  # Data(10)

del d
# پس از آزادسازی، آیتم از cache حذف می‌شود خودکار

WeakValueDictionary آیتم‌های خود را بر اساس مقدار نگه می‌دارد؛ یعنی اگر هیچ ارجاع قوی دیگری به مقدار وجود نداشته باشد، ورودی از دیکشنری حذف می‌شود. این الگو برای کش‌های حافظه-محور مفید است که نمی‌خواهند اشیاء را دائماً زنده نگه دارند.

WeakKeyDictionary و WeakSet

WeakKeyDictionary مشابه WeakValueDictionary است اما کلیدها را به شکل ضعیف نگه می‌دارد — مناسب زمانی که می‌خواهید داده‌های مرتبط با اشیاء را نگهدارید ولی نباید عمر آن اشیاء را افزایش دهید. WeakSet مجموعه‌ای از ارجاعات ضعیف است.

proxy و WeakMethod

import weakref

class C:
    def method(self):
        print("method called")

c = C()
p = weakref.proxy(c)
p.method()  # کار می‌کند

del c
# حالا هر فراخوانی روی p باعث پرتاب ReferenceError می‌شود

weakref.proxy شیئی شبیه به ارجاع مستقیم برمی‌گرداند اما ضعیف است. این proxy شبیه دسترسی مستقیم به نمونه رفتار می‌کند تا زمانی که شی زنده باشد؛ بعد از جمع‌آوری، استفاده از proxy باعث ReferenceError خواهد شد. این در مواقعی مفید است که می‌خواهید API شبیه شی واقعی ارائه دهید بدون نگهداشتن آن در حافظه.

import weakref
import types

class A:
    def method(self):
        print("hello")

a = A()
wm = weakref.WeakMethod(a.method)
f = wm()
f()  # hello

del a
# پس از حذف، wm() برابر None می‌شود یا فراخوانی ناممکن خواهد بود

WeakMethod برای نگهداری ارجاع‌های ضعیف به متدهای نمونه مفید است؛ این کمک می‌کند زمانی که کال‌بک‌ها به متدهای نمونه اشاره دارند، از نگه داشتن اشیاء جلوگیری شود.

finalize — جایگزین مطمئن برای __del__

import weakref
import tempfile
import os

f = tempfile.NamedTemporaryFile(delete=False)
path = f.name
f.close()

def cleanup(path):
    try:
        os.remove(path)
        print("Removed", path)
    except FileNotFoundError:
        pass

obj = object()
weakref.finalize(obj, cleanup, path)
del obj
# وقتی obj جمع‌آوری شود، cleanup اجرا می‌شود

weakref.finalize تابعی امن برای ثبت عملیات پاکسازی فراهم می‌کند که مستقل از روش چندنخی و مشکلات __del__ عمل می‌کند. این متد معمولا جایگزین بهتری برای destructor در سناریوهای واقعی است چون ترتیب اجرای finalizerها کنترل‌شده‌تر و ایمن‌تر است.

مثال پیشرفته: کش با WeakValueDictionary و نگهداری متغیر اضافی

import weakref

class Big:
    def __init__(self, name):
        self.name = name

cache = weakref.WeakValueDictionary()
refs = {}  # برای نگهداری اطلاعات متادیتا

def get_or_create(name):
    obj = cache.get(name)
    if obj is None:
        obj = Big(name)
        cache[name] = obj
        # نگهداری متادیتا بدون جلوگیری از جمع‌آوری خود شی
        refs[name] = weakref.ref(obj, lambda wr, n=name: refs.pop(n, None))
    return obj

در این کد از WeakValueDictionary برای خود اشیاء استفاده می‌کنیم و یک دیکشنری جداگانه برای متادیتا داریم. نکتهٔ کلیدی استفاده از callback روی weakref برای پاکسازی متادیتا است تا پس از جمع‌آوری شی، اطلاعات مرتبط هم حذف شود.

مقایسهٔ سریع (جدول)

نوعآیا ارجاع ضعیف است؟موارد استفاده
weakref.refبلهپیگیری تک‌اشیاء، callback هنگام جمع‌آوری
WeakValueDictionaryبله (برای مقادیر)کش‌هایی که نباید عمر اشیاء را افزایش دهند
WeakKeyDictionaryبله (برای کلیدها)جاسازی داده‌ مرتبط با اشیاء بدون جلوگیری از جمع‌آوری
WeakSetبلهمجموعه‌ای از اشیاء نگهداری‌شونده به‌صورت ضعیف
proxy / WeakMethodبلهدسترسی شبیه به شی یا متد بدون نگهداشتن آن

نکات مهم و دام‌ها

  • اشیاء ابتدایی مثل int و str معمولا قابلیت weakref ندارند مگر صراحتا پیاده‌سازی شده باشند (مثلاً اشیاء کاربر تعریف‌شده).
  • weakref باعث سریع‌تر شدن حافظه نمی‌شود؛ بلکه اجازه می‌دهد اشیاء زمانی که دیگر استفاده نمی‌شوند آزاد شوند. استفادهٔ اشتباه می‌تواند به باگ‌هایی مانند دسترسی به None یا ReferenceError منجر شود.
  • در برنامه‌های چندنخی، callbackهای weakref باید ایمن از لحاظ thread-safe باشند.
  • از weakref.finalize برای عملیات پاکسازی پیچیده استفاده کنید؛ __del__ می‌تواند به‌خاطر ترتیب اجرای ناپایدار مشکلاتی ایجاد کند.

خلاصه و توصیه‌ها

کتابخانهٔ weakref ابزار قدرتمندی برای مدیریت عمر اشیاء و طراحی کش‌های حافظه-دوست است. آن را زمانی به‌کار ببرید که می‌خواهید نگهداری اطلاعات مرتبط بدون جلوگیری از آزادسازی شی‌ها را ممکن کنید. برای پاکسازی‌های قابل‌اطمینان از weakref.finalize استفاده کنید و هنگام استفاده از proxies و callbacks، رفتار بعد از جمع‌آوری را در نظر داشته باشید تا باگ‌های ناخوشایند و استثناء‌های سخت پیدا نشود.

آیا این مطلب برای شما مفید بود ؟

خیر
بله
موضوعات شما در انجمن: