کتابخانه 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، رفتار بعد از جمعآوری را در نظر داشته باشید تا باگهای ناخوشایند و استثناءهای سخت پیدا نشود.
آیا این مطلب برای شما مفید بود ؟




