ویژگی تصویر

کتابخانه contextlib در پایتون — راهنمای کامل

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

کتابخانه استاندارد contextlib در پایتون مجموعه‌ای از ابزارها برای ساخت و کار با context managerها فراهم می‌کند. Context managerها (آن‌هایی که از with استفاده می‌کنند) برای مدیریت منابع، نظافت (cleanup) و جلوگیری از نشت منابع بسیار حیاتی هستند. در این مقاله به صورت عملی و با مثال‌های واقعی، امکانات مهم contextlib را بررسی می‌کنیم.

چرا contextlib مهم است؟

  • کد خواناتر و امن‌تر می‌سازد (مثلاً باز و بسته کردن فایل یا قفل‌ها).
  • مدیریت استثناها و تضمین cleanup را ساده می‌کند.
  • ابزارهای آماده برای موارد پیچیده‌تر مانند چندین resource هم‌زمان یا context managerهای تولیدی فراهم می‌آورد.

مقایسهٔ کوتاه ابزارهای مهم

تابع/کلاسکاربرد
contextmanagerساختن context manager از یک generator
suppressنادیده گرفتن استثناهای مشخص
ExitStackمدیریت پشته‌ای از contextها به صورت داینامیک
redirect_stdout / redirect_stderrری‌دایرکت خروجی استاندارد به یک فایل یا شیء شبیه فایل
nullcontextیک context manager خنثی، مفید در زمان عدم نیاز به context در حالت‌های مختلف

ساختن context manager با @contextmanager

from contextlib import contextmanager

@contextmanager
def open_file(path, mode='r'):
    f = open(path, mode)
    try:
        yield f
    finally:
        f.close()

# استفاده
with open_file('example.txt', 'w') as f:
    f.write('hello')

این کد نشان می‌دهد چطور با دکوراتور @contextmanager می‌توانید یک generator تعریف کرده و قبل از yield کارهای آماده‌سازی و بعد از آن کارهای cleanup را انجام دهید. در مثال بالا فایل همیشه بسته می‌شود حتی اگر داخل بلوک استثنا رخ دهد.

suppress — نادیده گرفتن استثناهای مشخص

from contextlib import suppress

with suppress(FileNotFoundError):
    os.remove('maybe_missing.txt')

با suppress می‌توان استثناهای مشخص را نادیده گرفت. این برای مواقعی که حذف فایل یا آزادسازی منبع اختیاری است و نمی‌خواهیم با FileNotFoundError برنامه متوقف شود مفید است.

ExitStack — مدیریت چندین context به صورت داینامیک

from contextlib import ExitStack

files = ['a.txt', 'b.txt', 'c.txt']
with ExitStack() as stack:
    opened = [stack.enter_context(open(f, 'r')) for f in files]
    # حالا می‌توانیم روی opened کار کنیم
    contents = [f.read() for f in opened]

ExitStack اجازه می‌دهد تا تعداد و نوع contextها در زمان اجرا تعیین شوند. این روش از نوشتن بلوک‌های تو در تو جلوگیری می‌کند و برای باز کردن مجموعه‌ای از فایل‌ها یا منابع که تعدادشان متغیر است مناسب است.

redirect_stdout و redirect_stderr

from contextlib import redirect_stdout
import io

buf = io.StringIO()
with redirect_stdout(buf):
    print('this goes to the buffer')

text = buf.getvalue()

این ابزار برای گرفتن خروجی تابع‌هایی که مستقیماً روی stdout چاپ می‌کنند مفید است (مثلاً در تست‌ها یا هنگام لاگ‌برداری موقت). در مثال، متن چاپ شده در یک StringIO ذخیره شده و بعداً قابل خواندن است.

nullcontext — زمانی که گاهی context لازم است و گاهی نه

from contextlib import nullcontext

cm = nullcontext()  # می‌تواند یک شی دلخواه به عنوان مقدار yield هم بگیرد
with cm:
    do_something()

در حالت‌هایی که تابعی بسته به شرایط یک context manager می‌پذیرد، می‌توان به جای شرط نوشتن دو مسیر، از nullcontext استفاده کرد تا یک context “بدون اثر” فراهم شود.

asynccontextmanager — برای کدهای غیرهم‌زمان

from contextlib import asynccontextmanager
import aiofiles

@asynccontextmanager
async def open_async(path, mode='r'):
    f = await aiofiles.open(path, mode)
    try:
        yield f
    finally:
        await f.close()

# استفاده در async
# async with open_async('x.txt', 'r') as f:
#     data = await f.read()

برای برنامه‌های async از asynccontextmanager استفاده کنید تا عملیات آماده‌سازی و پاک‌سازی ناهم‌زمان را به درستی مدیریت کنید. در مثال بالا از aiofiles برای باز کردن فایل به صورت async استفاده شده است.

نکات پیشرفته و بهترین شیوه‌ها

  • همیشه cleanup را در finally انجام دهید تا از نشت منابع جلوگیری شود.
  • برای مدیریت منابع پویا و ترکیبی از resourceها از ExitStack استفاده کنید.
  • در تست‌ها از redirect_stdout و suppress برای کنترل خروجی و جلوگیری از نویز لاگ استفاده کنید.
  • از nullcontext برای کاهش پیچیدگی شاخه‌های شرطی که با context کار می‌کنند بهره ببرید.
  • در طراحی API زمانبر یا حساس به منابع، پذیرش یک context manager به عنوان پارامتر می‌تواند انعطاف‌پذیری را بالا ببرد.

نمونهٔ بهینه‌سازی: تبدیل try/finally به contextmanager

# حالت قدیمی: try/finally
f = open('data.txt')
try:
    do_work(f)
finally:
    f.close()

# با contextmanager داخلی پایتون
with open('data.txt') as f:
    do_work(f)

هر زمان ممکن است از ساختار with استفاده کنید. اگر نیاز به منطق پیچیده‌تر دارید، تابع مورد نیاز را با @contextmanager بسازید تا کد تمیزتر و قابل نگهداری‌تر باشد.

نتیجه‌گیری و موارد کاربرد واقعی

contextlib ابزارهایی ساده ولی قدرتمند برای مدیریت منابع و رفتارهای جانبی فراهم می‌کند. از مدیریت فایل و قفل‌ها تا redirect خروجی و مدیریت contextهای پویا، این کتابخانه بخش مهمی از جعبه‌ابزار هر توسعه‌دهندهٔ پایتون است. آشنایی با contextmanager، ExitStack، suppress و دیگر امکانات contextlib باعث می‌شود کدی قابل اعتمادتر، قابل‌خواناتر و مطمئن‌تر بنویسید.

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

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