استفاده از Weakref در پایتون: مفاهیم متوسط
در پایتون، مدیریت حافظه به صورت خودکار توسط Garbage Collector (GC) انجام میشود. GC اشیاءی را که دیگر توسط هیچ متغیری ارجاع نشدهاند، شناسایی و از حافظه حذف میکند. اما گاهی اوقات، نیاز داریم تا به یک شیء ارجاع داشته باشیم بدون اینکه مانع از جمعآوری زباله آن توسط GC شویم. اینجاست که مفهوم Weak Reference یا ارجاع ضعیف به کمک میآید. در این مقاله، به بررسی عمیق Weakref در پایتون میپردازیم و کاربردهای آن را در سناریوهای مختلف بررسی خواهیم کرد.
Garbage Collection و ارجاعات قوی
قبل از پرداختن به Weakref، لازم است درک درستی از نحوه عملکرد Garbage Collection در پایتون داشته باشیم. پایتون از یک سیستم Garbage Collection مبتنی بر شمارش ارجاعات (Reference Counting) استفاده میکند. به این معنی که هر شیء یک شمارنده ارجاع دارد که تعداد متغیرهایی را که به آن شیء اشاره میکنند، نگهداری میکند. هر زمان که یک ارجاع جدید به شیء ایجاد شود، شمارنده ارجاع افزایش مییابد و هر زمان که یک ارجاع حذف شود (مثلاً متغیری از محدوده خارج شود یا مقدار آن تغییر کند)، شمارنده ارجاع کاهش مییابد. زمانی که شمارنده ارجاع به صفر برسد، GC شیء را از حافظه حذف میکند.
ارجاعاتی که به طور مستقیم به یک شیء اشاره میکنند (مانند `x = object()`)، ارجاعات قوی (Strong References) نامیده میشوند. ارجاعات قوی مانع از جمعآوری زباله شیء میشوند، حتی اگر دیگر به آن نیاز نباشد. این موضوع میتواند منجر به مشکلاتی مانند نشت حافظه (Memory Leak) شود، به خصوص در برنامههای بزرگ و پیچیده.
Weakref چیست؟
Weak Reference یا ارجاع ضعیف، نوعی ارجاع به یک شیء است که مانع از جمعآوری زباله آن نمیشود. به عبارت دیگر، اگر تنها ارجاع به یک شیء یک ارجاع ضعیف باشد، GC میتواند آن شیء را از حافظه حذف کند. Weakref به ما امکان میدهد تا به یک شیء ارجاع داشته باشیم بدون اینکه مالکیت آن را به دست بگیریم و از جمعآوری زباله آن جلوگیری کنیم.
ماژول `weakref` در پایتون
پایتون ماژول `weakref` را برای کار با ارجاعات ضعیف فراهم میکند. این ماژول توابعی را برای ایجاد و مدیریت Weak Reference ارائه میدهد. مهمترین توابع این ماژول عبارتند از:
- `weakref.ref(object[, callback])`: یک Weak Reference به شیء `object` ایجاد میکند. `callback` یک تابع اختیاری است که زمانی که شیء جمعآوری میشود، فراخوانی میشود.
- `weakref.proxy(object[, callback])`: یک Weak Reference به شیء `object` ایجاد میکند که به صورت یک پراکسی (Proxy) عمل میکند. این پراکسی به گونهای رفتار میکند که انگار یک ارجاع قوی است، اما اگر شیء جمعآوری شود، دسترسی به آن باعث ایجاد یک خطا میشود.
نحوه استفاده از `weakref.ref()`
برای ایجاد یک Weak Reference با استفاده از `weakref.ref()`، ابتدا شیء مورد نظر را ایجاد میکنیم و سپس یک Weak Reference به آن ایجاد میکنیم. در اینجا یک مثال ساده آورده شده است:
import weakref
class MyObject:
def __init__(self, name):
self.name = name
def __del__(self):
print(f"شیء {self.name} حذف شد.")
obj = MyObject("مثال")
weak_ref = weakref.ref(obj)
print(f"آیا شیء هنوز وجود دارد؟ {weak_ref() is not None}")
del obj # حذف ارجاع قوی
import gc
gc.collect() # اجرای دستی Garbage Collector
print(f"آیا شیء هنوز وجود دارد؟ {weak_ref() is not None}")
در این مثال، ابتدا یک شیء از کلاس `MyObject` ایجاد میکنیم. سپس یک Weak Reference به این شیء با استفاده از `weakref.ref()` ایجاد میکنیم. در ابتدا، `weak_ref()` به شیء اشاره میکند و مقدار آن `True` است. اما پس از حذف ارجاع قوی (`del obj`) و اجرای Garbage Collector (`gc.collect()`)، شیء از حافظه حذف میشود و `weak_ref()` مقدار `None` را برمیگرداند.
نحوه استفاده از `weakref.proxy()`
`weakref.proxy()` یک روش دیگر برای ایجاد Weak Reference است که به صورت یک پراکسی عمل میکند. این پراکسی به گونهای رفتار میکند که انگار یک ارجاع قوی است، اما اگر شیء جمعآوری شود، دسترسی به آن باعث ایجاد یک خطا میشود. در اینجا یک مثال آورده شده است:
import weakref
class MyObject:
def __init__(self, name):
self.name = name
def __del__(self):
print(f"شیء {self.name} حذف شد.")
obj = MyObject("مثال")
weak_proxy = weakref.proxy(obj)
print(f"نام شیء: {weak_proxy.name}")
del obj
import gc
gc.collect()
try:
print(f"نام شیء: {weak_proxy.name}")
except ReferenceError:
print("شیء جمعآوری شده است.")
در این مثال، `weak_proxy` به عنوان یک پراکسی به شیء `obj` اشاره میکند. در ابتدا، میتوانیم به ویژگیهای شیء از طریق `weak_proxy` دسترسی داشته باشیم. اما پس از حذف ارجاع قوی و اجرای Garbage Collector، دسترسی به `weak_proxy.name` باعث ایجاد یک `ReferenceError` میشود.
کاربردهای Weakref
Weakref در سناریوهای مختلفی کاربرد دارد، از جمله:
- Cacheها: در Cacheها، اغلب نیاز داریم تا اشیاء را برای مدت زمان مشخصی ذخیره کنیم. با استفاده از Weakref، میتوانیم اشیاء را در Cache ذخیره کنیم بدون اینکه مانع از جمعآوری زباله آنها شویم.
- Callbackها: در برخی موارد، نیاز داریم تا یک Callback را به یک شیء متصل کنیم. با استفاده از Weakref، میتوانیم Callback را به شیء متصل کنیم بدون اینکه مانع از جمعآوری زباله آن شویم.
- Observer Pattern: در Observer Pattern، یک شیء (Observer) به تغییرات در یک شیء دیگر (Subject) واکنش نشان میدهد. با استفاده از Weakref، میتوانیم Observerها را به Subject متصل کنیم بدون اینکه مانع از جمعآوری زباله آنها شویم.
- ساختارهای دادهای پیچیده: در ساختارهای دادهای پیچیده مانند گرافها، استفاده از Weakref میتواند به جلوگیری از نشت حافظه کمک کند.
Callbackها در Weakref
همانطور که قبلاً اشاره شد، میتوانیم یک Callback را به یک Weak Reference متصل کنیم. این Callback زمانی فراخوانی میشود که شیء جمعآوری شود. این قابلیت میتواند برای انجام کارهای خاصی مانند پاکسازی منابع یا ثبت رویدادها مفید باشد. در اینجا یک مثال آورده شده است:
import weakref
def callback(ref):
print("شیء جمعآوری شد.")
obj = object()
weak_ref = weakref.ref(obj, callback)
del obj
import gc
gc.collect()
در این مثال، یک Callback به نام `callback` را به Weak Reference متصل میکنیم. زمانی که شیء `obj` جمعآوری میشود، Callback فراخوانی میشود و پیام “شیء جمعآوری شد.” چاپ میشود.
نکات مهم در استفاده از Weakref
هنگام استفاده از Weakref، باید به نکات زیر توجه داشته باشید:
- Weakref تنها زمانی مفید است که شیء دیگری به آن ارجاع نداشته باشد. اگر شیء دیگری به آن ارجاع داشته باشد، GC آن را جمعآوری نمیکند.
- دسترسی به یک Weak Reference که به یک شیء جمعآوری شده اشاره میکند، باعث ایجاد یک `ReferenceError` میشود.
- استفاده بیش از حد از Weakref میتواند منجر به کاهش کارایی برنامه شود.
نتیجهگیری
Weakref یک ابزار قدرتمند در پایتون است که به ما امکان میدهد تا به اشیاء ارجاع داشته باشیم بدون اینکه مانع از جمعآوری زباله آنها شویم. با درک صحیح نحوه عملکرد Weakref و کاربردهای آن، میتوانیم برنامههای پایتون کارآمدتر و قابل اعتمادتری ایجاد کنیم. استفاده از Weakref در سناریوهایی مانند Cacheها، Callbackها و Observer Pattern میتواند به جلوگیری از نشت حافظه و بهبود کارایی برنامه کمک کند.

بدون دیدگاه