خانه / آموزش‌ها / مدیریت حافظه و Garbage Collection در پایتون

مدیریت حافظه و Garbage Collection در پایتون

🐍 HomeOfPython
|
📅 1404/10/17

مدیریت حافظه (Memory Management) یکی از مهم‌ترین جنبه‌های هر زبان برنامه‌نویسی است. در پایتون، برخلاف زبان‌هایی مانند C یا ++C، شما نیازی به تخصیص و آزادسازی دستی حافظه ندارید. پایتون از یک سیستم خودکار به نام Garbage Collector (زباله‌روب) استفاده می‌کند تا حافظه‌ی اشیایی که دیگر استفاده نمی‌شوند را آزاد کند.

در این مقاله، از مفاهیم اولیه نحوه کارکرد متغیرها در حافظه شروع کرده و تا عمیق‌ترین مباحث ماژول gc و بهینه‌سازی حافظه پیش می‌رویم.

چرخه حیات اشیا در پایتون

سطح مقدماتی (Beginner Level)

در سطح مقدماتی، باید بدانیم که پایتون چگونه متغیرها را ذخیره می‌کند و مکانیسم اصلی آن برای آزادسازی حافظه چیست.

۱. مفهوم متغیرها و ارجاع‌ها (References)

در پایتون، متغیرها جعبه‌هایی نیستند که داده را در خود نگه دارند؛ بلکه برچسب‌هایی (Labels) هستند که به اشیا در حافظه اشاره می‌کنند. وقتی می‌نویسید a = 10، یک شیء عدد صحیح 10 در حافظه ساخته می‌شود و a به آن اشاره می‌کند.

مثال ۱: تخصیص ساده (Static)

این کد صرفاً یک انتساب است و خروجی خاصی ندارد، بنابراین از بلاک استاتیک استفاده می‌کنیم.

python
# متغیر x به یک لیست در حافظه اشاره می‌کند
x = [1, 2, 3]

# متغیر y هم به همان لیست اشاره می‌کند (کپی نمی‌شود، فقط ارجاع جدید ساخته می‌شود)
y = x

۲. شمارش ارجاع (Reference Counting)

مکانیسم اصلی مدیریت حافظه در پایتون، "شمارش ارجاع" است. هر شیء در پایتون یک شمارنده دارد که نشان می‌دهد چند متغیر به آن اشاره می‌کنند.

  • وقتی متغیری به شیء اشاره کند، شمارنده افزایش می‌یابد.
  • وقتی متغیر حذف شود یا به چیز دیگری اشاره کند، شمارنده کاهش می‌یابد.
  • وقتی شمارنده به صفر برسد، حافظه بلافاصله آزاد می‌شود.

مثال ۲: بررسی تعداد ارجاعات (Interactive)

ما می‌توانیم با استفاده از ماژول sys تعداد ارجاعات به یک شیء را ببینیم. (توجه: sys.getrefcount همیشه یکی بیشتر نشان می‌دهد چون خود آرگومان تابع هم یک ارجاع موقت است).

Python

مثال ۳: حذف ارجاع با del (Interactive)

وقتی از دستور del استفاده می‌کنیم، شیء پاک نمی‌شود، بلکه نام متغیر پاک شده و یکی از ارجاعات شیء کم می‌شود.

Python

۳. محدودیت شمارش ارجاع

شمارش ارجاع بسیار سریع است، اما یک مشکل بزرگ دارد: نمی‌تواند ارجاعات دوری (Cyclic References) را تشخیص دهد. اگر شیء A به B اشاره کند و B به A، شمارنده هرگز صفر نمی‌شود، حتی اگر کل برنامه دیگر به آن‌ها دسترسی نداشته باشد. اینجاست که Garbage Collector وارد می‌شود.

python
# مثال استاتیک از یک چرخه (Cycle)
a = []
b = []
a.append(b) # a به b اشاره می‌کند
b.append(a) # b به a اشاره می‌کند
# حتی اگر a و b را del کنیم، این دو شیء یکدیگر را نگه داشته‌اند.

سطح پیشرفته (Professional Level)

در این سطح به سراغ ماژول gc، نسل‌های زباله‌روبی (Generational GC) و مدیریت حرفه‌ای حافظه برای جلوگیری از نشت حافظه (Memory Leak) می‌رویم.

۱. نسل‌های زباله‌روب (Generational Garbage Collection)

پایتون برای حل مشکل ارجاعات دوری، از یک الگوریتم تکمیلی استفاده می‌کند که اشیا را در سه نسل (Generation) دسته‌بندی می‌کند:

  1. نسل ۰ (Generation 0): اشیای تازه ساخته شده. پرسرعت‌ترین اسکن در اینجا انجام می‌شود.
  2. نسل ۱ (Generation 1): اشیایی که از اسکن نسل ۰ جان سالم به در برده‌اند.
  3. نسل ۲ (Generation 2): اشیای قدیمی که مدت زیادی در حافظه مانده‌اند.

وقتی تعداد اشیای نسل ۰ از یک آستانه (Threshold) بگذرد، GC اجرا می‌شود.

نسل‌های GC

مثال ۱: بررسی آستانه‌های GC (Interactive)

این کد آستانه‌های پیش‌فرض پایتون برای هر نسل را نمایش می‌دهد.

Python

۲. اجبار به اجرای زباله‌روبی (Manual Collection)

گاهی اوقات در برنامه‌های سنگین (مثل پردازش تصویر یا هوش مصنوعی)، می‌خواهیم دقیقاً در یک زمان خاص حافظه آزاد شود تا از مکث‌های ناگهانی برنامه جلوگیری کنیم. متد gc.collect() این کار را انجام می‌دهد.

مثال ۲: ساخت چرخه و آزادسازی دستی (Interactive)

در این مثال، یک ارجاع دوری می‌سازیم که Reference Counting نمی‌تواند آن را پاک کند، سپس با gc.collect آن را تمیز می‌کنیم.

Python

۳. ارجاعات ضعیف (Weak References)

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

این روش برای کش‌سازی (Caching) بسیار کاربردی است.

مثال ۳: استفاده از weakref (Interactive)

در این مثال می‌بینیم که weakref مانع از پاک شدن شیء نمی‌شود.

Python

۴. نکات فنی و Best Practiceها

  • پرهیز از __del__: تا حد امکان از متد مخرب __del__ استفاده نکنید. در نسخه‌های قدیمی پایتون، اگر دو شیء در یک چرخه __del__ داشتند، پایتون نمی‌دانست کدام را اول پاک کند و هر دو را در حافظه نگه می‌داشت (Uncollectable garbage). هرچند در پایتون ۳.۴+ این مشکل تا حد زیادی حل شده، اما استفاده از Context Managers (with statement) بسیار ایمن‌تر است.
  • مدیریت لیست‌های بزرگ: اگر لیستی دارید که مرتب به آن اضافه و کم می‌شود، گاهی gc.collect() را دستی صدا بزنید یا از collections.deque استفاده کنید که مدیریت حافظه بهتری دارد.

مثال ۴: استفاده از اسلات‌ها برای کاهش حافظه (Static)

استفاده از __slots__ در کلاس‌ها باعث می‌شود پایتون به جای دیکشنری (__dict__) از آرایه ثابت برای صفات استفاده کند که مصرف حافظه را شدیداً کاهش می‌دهد.

python
class EfficientClass:
    # محدود کردن صفات کلاس و حذف __dict__
    __slots__ = ['x', 'y']
    
    def __init__(self, x, y):
        self.x = x
        self.y = y