Context Manager در پایتون چیست؟

آموزش دستور with، متدهای __enter__ و __exit__ و ساخت Context Manager در پایتون

Context Manager چیست؟

در برنامه‌نویسی گاهی لازم است قبل از انجام یک کار، یک منبع را آماده کنیم و بعد از پایان کار آن را آزاد کنیم.

برای مثال هنگام کار با فایل، معمولاً باید فایل را باز کنیم، اطلاعات موردنظر را بخوانیم یا بنویسیم و در نهایت فایل را ببندیم.

پایتون برای مدیریت ساده و مطمئن چنین منابعی قابلیتی به نام Context Manager در اختیار ما قرار می‌دهد.

معروف‌ترین شکل استفاده از Context Manager در پایتون، دستور with است.

دستور with در پایتون چیست؟

دستور with کمک می‌کند یک منبع را برای مدت مشخصی در اختیار داشته باشیم و پس از پایان کار، عملیات لازم برای آزادسازی آن منبع به‌صورت خودکار انجام شود.

یک نمونه بسیار ساده برای کار با فایل:

with open("data.txt", "r") as file:
    content = file.read()

print(content)

در این مثال فایل باز می‌شود و داخل بلوک with مورد استفاده قرار می‌گیرد.

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

چرا به جای روش معمول از with استفاده کنیم؟

می‌توان فایل را بدون with نیز باز کرد:

file = open("data.txt", "r")

content = file.read()

file.close()

این روش در مثال ساده بالا کار می‌کند، اما اگر هنگام اجرای عملیات روی فایل خطایی رخ دهد، ممکن است دستور close() اجرا نشود.

استفاده از with باعث می‌شود مدیریت منابع قابل اعتمادتر و کد خواناتر باشد.

with و مدیریت خطا

یکی از مزایای مهم Context Manager این است که حتی اگر داخل بلوک with خطایی رخ دهد، عملیات خروج از Context انجام می‌شود.

with open("data.txt", "r") as file:
    data = file.read()

    print(data)

    # ممکن است اینجا خطایی رخ دهد

پس از پایان Context، فایل بسته خواهد شد. به همین دلیل with برای مدیریت منابع بسیار مفید است.

Context Manager چگونه کار می‌کند؟

یک Context Manager معمولاً دو متد مهم دارد:

  • __enter__()
  • __exit__()

متد __enter__ هنگام ورود به Context اجرا می‌شود.

متد __exit__ هنگام خروج از Context اجرا می‌شود.

متد __enter__ چیست؟

متد __enter__ زمانی اجرا می‌شود که برنامه وارد بلوک with می‌شود.

class MyContext:

    def __enter__(self):
        print("ورود به Context")

        return self

مقداری که از __enter__ برگردانده می‌شود، می‌تواند با as دریافت شود.

متد __exit__ چیست؟

متد __exit__ هنگام خروج از بلوک with اجرا می‌شود.

class MyContext:

    def __exit__(self, exc_type, exc_value, traceback):
        print("خروج از Context")

پارامترهای این متد اطلاعات مربوط به خطای احتمالی را نیز در اختیار Context Manager قرار می‌دهند.

ساخت یک Context Manager ساده

حالا یک Context Manager ساده می‌سازیم:

class MyContext:

    def __enter__(self):
        print("شروع کار")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("پایان کار")


with MyContext() as context:

    print("داخل Context هستیم")

خروجی تقریباً به شکل زیر خواهد بود:

شروع کار
داخل Context هستیم
پایان کار

استفاده از as در Context Manager

می‌توان نتیجه متد __enter__ را با as دریافت کرد.

class Database:

    def __enter__(self):
        print("اتصال به دیتابیس")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("بستن اتصال")


with Database() as db:

    print("در حال استفاده از دیتابیس")

در این مثال متغیر db همان مقداری است که متد __enter__ برگردانده است.

کتابخانه contextlib چیست؟

پایتون کتابخانه‌ای به نام contextlib دارد که ساخت Context Managerها را ساده‌تر می‌کند.

یکی از ابزارهای مهم این کتابخانه، دکوراتور @contextmanager است.

ساخت Context Manager با @contextmanager

با استفاده از @contextmanager می‌توان بدون نوشتن مستقیم __enter__ و __exit__ یک Context Manager ساخت.

from contextlib import contextmanager


@contextmanager
def my_context():

    print("شروع Context")

    yield

    print("پایان Context")


with my_context():

    print("داخل Context")

خروجی:

شروع Context
داخل Context
پایان Context

دستور yield در اینجا نقطه‌ای را مشخص می‌کند که کنترل وارد بدنه with می‌شود.

استفاده از try و finally در Context Manager

برای اینکه عملیات پایانی حتی هنگام رخ دادن خطا انجام شود، معمولاً از try و finally استفاده می‌کنیم.

from contextlib import contextmanager


@contextmanager
def my_context():

    print("شروع کار")

    try:
        yield
    finally:
        print("پاکسازی منابع")


with my_context():

    print("انجام عملیات")

بخش finally برای عملیات Cleanup بسیار مهم است.

مثال کاربردی: اندازه‌گیری زمان اجرای برنامه

می‌توان از Context Manager برای اندازه‌گیری زمان اجرای یک بخش از برنامه استفاده کرد.

from contextlib import contextmanager
import time


@contextmanager
def timer():

    start = time.time()

    try:
        yield
    finally:
        end = time.time()

        print("زمان اجرا:", end - start)


with timer():

    time.sleep(2)

این Context Manager قبل از اجرای کد زمان شروع را ذخیره می‌کند و پس از پایان کار مدت زمان اجرای کد را نمایش می‌دهد.

Context Manager و فایل‌ها

یکی از رایج‌ترین نمونه‌های Context Manager در پایتون، تابع open() است.

with open("python.txt", "w", encoding="utf-8") as file:

    file.write("PythonAI")

پس از پایان بلوک with، فایل بسته می‌شود.

این روش باعث می‌شود لازم نباشد هر بار به‌صورت دستی file.close() را بنویسیم.

تفاوت Context Manager و Decorator

Decorator و Context Manager هر دو برای مدیریت و تغییر رفتار برنامه کاربرد دارند، اما هدف یکسانی ندارند.

  • Decorator: رفتار یک تابع یا کلاس را تغییر یا گسترش می‌دهد.
  • Context Manager: ورود و خروج از یک محدوده مشخص از کد یا مدیریت منابع را کنترل می‌کند.

در درس قبلی با Decoratorها آشنا شدیم و اکنون Context Manager یکی دیگر از ابزارهای مهم پایتون است.

کاربردهای Context Manager

Context Managerها در پروژه‌های واقعی کاربردهای زیادی دارند.

  • باز کردن و بستن فایل‌ها
  • اتصال و قطع اتصال دیتابیس
  • مدیریت Lock در برنامه‌های چندنخی
  • مدیریت منابع سیستم
  • مدیریت تراکنش‌های دیتابیس
  • اندازه‌گیری زمان اجرای کد
  • مدیریت Session و Connection
  • پاکسازی منابع پس از پایان عملیات

اشتباهات رایج در استفاده از Context Manager

۱. فراموش کردن finally

هنگام ساخت Context Manager با @contextmanager بهتر است عملیات Cleanup را در بخش finally قرار دهیم.

۲. استفاده نادرست از yield

در Context Managerهای ساخته‌شده با @contextmanager باید ساختار تابع به‌درستی طراحی شود تا ورود و خروج Context مشخص باشد.

۳. پیچیده کردن بیش از حد Context Manager

اگر یک Context Manager ساده را می‌توان با چند خط کد واضح نوشت، نیازی نیست آن را بیش از حد پیچیده کنیم.

جمع‌بندی Context Manager در پایتون

Context Manager یکی از قابلیت‌های مهم پایتون برای مدیریت منابع و کنترل ورود و خروج از یک محدوده مشخص است.

مهم‌ترین ابزار برای استفاده از آن، دستور with است.

همچنین با متدهای __enter__ و __exit__ می‌توان Context Manager اختصاصی ساخت.

کتابخانه contextlib نیز ساخت Context Managerها را ساده‌تر می‌کند و @contextmanager یکی از کاربردی‌ترین ابزارهای آن است.

یادگیری Context Manager برای پروژه‌های واقعی پایتون، مخصوصاً کار با فایل‌ها، دیتابیس و منابع مختلف، بسیار مفید است.

تمرین‌های Context Manager

  1. یک Context Manager بسازید که هنگام ورود عبارت شروع برنامه و هنگام خروج عبارت پایان برنامه را نمایش دهد.
  2. یک Context Manager با @contextmanager بنویسید که زمان اجرای یک قطعه کد را اندازه‌گیری کند.
  3. یک Context Manager برای مدیریت یک فایل متنی ایجاد کنید.
  4. بررسی کنید اگر داخل بلوک with خطایی رخ دهد، متد __exit__ چگونه اجرا می‌شود.

سوالات متداول

Context Manager در پایتون چیست؟

Context Manager ابزاری برای مدیریت ورود و خروج از یک محدوده مشخص و معمولاً مدیریت منابعی مانند فایل، اتصال دیتابیس یا Lock است.

دستور with چه کاری انجام می‌دهد؟

دستور with ورود و خروج از یک Context را مدیریت می‌کند و امکان انجام عملیات Cleanup را فراهم می‌سازد.

__enter__ و __exit__ چه هستند؟

این دو متد برای ساخت Context Managerهای مبتنی بر کلاس استفاده می‌شوند. __enter__ هنگام ورود و __exit__ هنگام خروج اجرا می‌شود.

@contextmanager چیست؟

@contextmanager یک دکوراتور از کتابخانه contextlib است که ساخت Context Manager را با استفاده از یک تابع و yield ساده می‌کند.

آیا with فقط برای فایل‌ها استفاده می‌شود؟

خیر. Context Manager می‌تواند برای فایل‌ها، دیتابیس، Lock، Session، Connection و بسیاری از منابع دیگر استفاده شود.