FA-TOOLS — Header Component
آموزش context manager و دستور with در پایتون

آموزش Context Manager و دستور with در پایتون

سلام رفیق برنامه‌نویس! اگه دنبال یه راه حرفه‌ای برای مدیریت منابع توی پایتون هستی که کدات هم تمیزتر و امن‌تر باشن، جای درستی اومدی. امروز می‌خوایم غرق بشیم توی دنیای Context Managerها و دستور with. این دو تا از اون ابزارهای طلایی پایتون هستن که کار با فایل‌ها، دیتابیس‌ها، قفل‌ها و هر منبعی که نیاز به باز و بسته شدن دقیق داره رو برات آب خوردن می‌کنن. آماده‌ای یه قدم بزرگ تو کدنویسیت برداری؟ راستی، اگه دنبال یه انبوهی از ابزارهای برنامه‌نویسی خفن برای کارات می‌گردی، حتماً یه سر به سایتمون بزن، پشیمون نمیشی!

✨ خلاصه راهنمای سریع: Context Manager در پایتون ✨

آموزش context manager و دستور with در پایتون — تصویر 1

✅ دستور with

  • 🔹 مدیریت خودکار منابع (فایل، دیتابیس، قفل).
  • 🔹 تضمین تمیزکاری (چه خطا رخ بده، چه نه).
  • 🔹 خوانایی و امنیت کد بالا.

💡 Context Manager

  • 🔹 آبجکتی که متدهای __enter__ و __exit__ رو پیاده‌سازی می‌کنه.
  • 🔹 مسئول تنظیم و آزادسازی منابع.
  • 🔹 می‌تونی خودت هم بسازیشون!

📞 سوالی داری؟ با ما در تماس باش!

09202232789

چرا Context Managerها اینقدر مهمن؟ (مشکل چی بود؟)

آموزش context manager و دستور with در پایتون — تصویر 2

توی برنامه‌نویسی، خیلی وقت‌ها با منابعی سر و کار داریم که باید بعد از استفاده، حتماً بسته یا آزاد بشن. مثلاً باز کردن یه فایل برای خوندن یا نوشتن، ایجاد اتصال به دیتابیس، یا حتی گرفتن یه قفل (Lock) توی برنامه‌نویسی موازی. اگه این منابع رو درست آزاد نکنیم، چه اتفاقی میفته؟ منابع سیستم اشغال میشن، ممکنه حافظه کم بیاریم (Memory Leak)، اطلاعاتمون خراب بشه یا حتی برنامه‌مون قفل کنه.

قبل از اینکه Context Managerها بیان، برای مدیریت این جور منابع باید از ساختارهای try...finally استفاده می‌کردیم. بیا یه نگاهی بهش بندازیم:

روی کد کلیک کن تا کپی بشه:


file = None
try:
file = open('my_data.txt', 'w')
file.write('این یک متن آزمایشی است.')
# فرض کنید اینجا یه خطای غیرمنتظره رخ میده
raise ValueError("خطای ساختگی!")
except Exception as e:
print(f"خطا رخ داد: {e}")
finally:
if file:
file.close()
print("فایل با موفقیت بسته شد.")

همونطور که می‌بینی، این روش کار می‌کنه ولی یه کم شلوغه و احتمال داره فراموش کنی close() رو صدا بزنی یا کد finally رو درست ننویسی. اینجا بود که پایتون با Context Managerها و دستور with به کمکمون اومد.

دستور with: معجزه‌ای برای مدیریت منابع

آموزش context manager و دستور with در پایتون — تصویر 3

دستور with یه روش بسیار شیک و پایتونیک برای کار با منابعیه که نیاز به راه‌اندازی (setup) و تمیزکاری (teardown) دارن. وقتی از with استفاده می‌کنی، پایتون تضمین می‌کنه که حتی اگه وسط کدهات خطا هم پیش بیاد، منبعت به درستی بسته میشه.

مثال کلاسیک: باز کردن فایل با with

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

روی کد کلیک کن تا کپی بشه:


# نوشتن در فایل
with open('my_new_data.txt', 'w') as file:
file.write('سلام، این متن از طریق with نوشته شده.n')
file.write('چه دنیای کدنویسی خوبی!n')
print("فایل my_new_data.txt نوشته و بسته شد.")

# خواندن از فایل
with open('my_new_data.txt', 'r') as file:
content = file.read()
print("nمحتوای فایل:")
print(content)
print("فایل my_new_data.txt خوانده و بسته شد.")

# حالا با یه خطا امتحانش می‌کنیم
try:
with open('error_file.txt', 'w') as file:
file.write('این فایل با خطا بسته میشه.n')
# ایجاد خطای ساختگی
1 / 0
except ZeroDivisionError:
print("nیک خطای تقسیم بر صفر رخ داد.")
# مهم: فایل error_file.txt اینجا به طور خودکار بسته شده!
print("فایل error_file.txt حتی با وجود خطا، بسته شد.")

دیدید چقدر کد تمیزتر و کوتاه‌تر شد؟ دیگه نگران file.close() نیستیم. پایتون خودش حواسش هست. برای اینکه عمیق‌تر با مفاهیم پایه‌ای پایتون آشنا بشی، می‌تونی به اسنیپت‌های پایتون ما یه نگاه بندازی.

Context Managerها چطور کار می‌کنن؟ (پشت پرده)

هر آبجکتی که متدهای __enter__ و __exit__ رو پیاده‌سازی کرده باشه، یک Context Manager به حساب میاد.

  • متد __enter__(self): این متد وقتی فراخونی میشه که کنترل جریان کد وارد بلوک with میشه. کارش معمولاً تنظیم و آماده‌سازی منبعه. هر مقداری که این متد برگردونه، به متغیر بعد از as (مثلاً file در with open(...) as file:) اختصاص داده میشه.
  • متد __exit__(self, exc_type, exc_val, exc_tb): این متد وقتی فراخونی میشه که کنترل از بلوک with خارج میشه، چه به صورت عادی و چه به خاطر یک خطا. کارش آزادسازی و تمیزکاری منبعه. پارامترهای exc_type، exc_val و exc_tb اطلاعات مربوط به خطایی رو که ممکنه در بلوک with رخ داده باشه، به ما میدن. اگه این متد True برگردونه، یعنی خطا هندل شده و نباید دوباره منتشر بشه.

ساخت Context Managerهای خودت (کلاس‌محور)

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

روی کد کلیک کن تا کپی بشه:


class DatabaseConnection:
def __init__(self, db_name):
self.db_name = db_name
self.connection = None
print(f"آبجکت DatabaseConnection برای '{db_name}' ایجاد شد.")

def __enter__(self):
print(f"🔥 __enter__: در حال باز کردن اتصال به دیتابیس '{self.db_name}'...")
# اینجا منطق اتصال به دیتابیس واقعی قرار می‌گیره
self.connection = f"Connection to {self.db_name}"
print("✅ اتصال باز شد.")
return self.connection # مقداری که به `as` اختصاص داده میشه

def __exit__(self, exc_type, exc_val, exc_tb):
print(f"💧 __exit__: در حال بستن اتصال به دیتابیس '{self.db_name}'...")
# اینجا منطق بستن اتصال به دیتابیس واقعی قرار می‌گیره
self.connection = None
print("⛔ اتصال بسته شد.")
if exc_type:
print(f"❌ خطایی در بلوک with رخ داد: {exc_type.__name__}, {exc_val}")
# اگه True برگردونیم، خطا سرکوب میشه
return False # خطا دوباره منتشر میشه
return False # اگه False برگردونیم، یعنی خطا رو هندل نکردیم

# استفاده از Context Manager سفارشی
print("--- مثال ۱: بدون خطا ---")
with DatabaseConnection("mydb") as db:
print(f"درون بلوک with: با استفاده از {db}")
# اینجا کارهای دیتابیسی انجام میشه
print("کارهای دیتابیسی انجام شد.")

print("n--- مثال ۲: با خطا ---")
try:
with DatabaseConnection("bad_db") as db:
print(f"درون بلوک with: با استفاده از {db}")
# فرض کن اینجا یه خطای دیتابیسی پیش میاد
raise ConnectionError("نمی‌توان به دیتابیس متصل شد!")
except ConnectionError as e:
print(f"مدیریت خطا در بیرون بلوک with: {e}")

حالا می‌تونی ببینی که چقدر Context Managerها مدیریت منابع رو راحت می‌کنن. حتی با وجود خطا، متد __exit__ فراخونی میشه و منابع آزاد میشن.

ماژول contextlib: راهی ساده‌تر برای Context Managerها (تابع‌محور)

اگه ساختن یه کلاس با دو تا متد __enter__ و __exit__ به نظرت زیاد میاد یا برای کارهای ساده‌تر دنبال یه راه سریع‌تری، پایتون ماژول contextlib رو داره. با استفاده از دکوراتور @contextmanager، می‌تونی یه تابع جنریتور ساده رو به یک Context Manager تبدیل کنی.

روی کد کلیک کن تا کپی بشه:


from contextlib import contextmanager

@contextmanager
def managed_resource(name):
print(f"🌟 __enter__: در حال راه‌اندازی منبع '{name}'...")
resource = f"Resource({name})"
try:
yield resource # اینجاست که کنترل به بلوک with منتقل میشه
finally:
print(f"🗑️ __exit__: در حال تمیزکاری منبع '{name}'...")
# اینجا منطق تمیزکاری یا بستن منبع واقعی قرار می‌گیره
print("✅ منبع تمیز شد.")

# استفاده از Context Manager تابع‌محور
print("--- مثال ۱: استفاده عادی ---")
with managed_resource("Printer") as printer:
print(f"درون بلوک with: با استفاده از {printer}")
print("در حال چاپ سند...")

print("n--- مثال ۲: با خطا ---")
try:
with managed_resource("Network") as net:
print(f"درون بلوک with: با استفاده از {net}")
raise TimeoutError("اتصال شبکه قطع شد!")
except TimeoutError as e:
print(f"مدیریت خطا در بیرون بلوک with: {e}")

همونطور که می‌بینی، کد بسیار خواناتر و مختصرتره. هر کدی قبل از yield معادل __enter__ و هر کدی بعد از yield (در بلوک finally) معادل __exit__ عمل می‌کنه.

کاربردهای واقعی Context Managerها

Context Managerها فقط برای فایل‌ها نیستن. اونا رو میشه تقریباً برای هر منبعی که نیاز به باز و بسته شدن یا آماده‌سازی و تمیزکاری داره، استفاده کرد.

۱. مدیریت قفل‌ها (Locks) در برنامه‌نویسی موازی

وقتی چند تا Thread یا Process دارن همزمان اجرا میشن، برای دسترسی به منابع مشترک (مثل یه متغیر سراسری) نیاز به قفل داریم تا از Race Condition جلوگیری کنیم. threading.Lock خودش یه Context Managerه.

روی کد کلیک کن تا کپی بشه:


import threading
import time

shared_data = 0
lock = threading.Lock()

def worker():
global shared_data
for _ in range(100_000):
with lock: # قفل کردن منبع
shared_data += 1
# قفل به صورت خودکار آزاد می‌شود

threads = []
for _ in range(5):
thread = threading.Thread(target=worker)
threads.append(thread)
thread.start()

for thread in threads:
thread.join()

print(f"مقدار نهایی shared_data: {shared_data}") # باید 500000 باشه

اگه این کد رو بدون with lock: اجرا کنی، مقدار shared_data ممکنه هر بار یه عدد دیگه باشه، چون Race Condition اتفاق میفته. اما با with، قفل به صورت خودکار گرفته و آزاد میشه و امنیت کد رو تضمین می‌کنه.

۲. مدیریت دایرکتوری‌های موقت (Temporary Directories)

گاهی اوقات نیاز داریم یه دایرکتوری موقت بسازیم، توش یه سری کارها انجام بدیم و بعد به طور کامل حذفش کنیم. ماژول tempfile این قابلیت رو با Context Manager فراهم می‌کنه.

روی کد کلیک کن تا کپی بشه:


import tempfile
import os

print("قبل از ایجاد دایرکتوری موقت.")
with tempfile.TemporaryDirectory() as tmpdir:
print(f"دایرکتوری موقت ایجاد شد: {tmpdir}")
temp_file_path = os.path.join(tmpdir, "my_temp_file.txt")
with open(temp_file_path, "w") as f:
f.write("این یه فایل توی دایرکتوری موقته.")
print(f"فایل در {temp_file_path} ایجاد شد.")
# اینجا می‌تونیم کارهای دیگه انجام بدیم...

print("بعد از بلوک with، دایرکتوری موقت به صورت خودکار حذف شد.")
# اگه تلاش کنی به tmpdir دسترسی پیدا کنی، خطا میده:
# print(os.path.exists(tmpdir)) # False

این قابلیت واقعاً عالیه، چون دیگه لازم نیست نگران پاک کردن فایل‌های موقت باشی و مطمئنی که سیستم فایل‌ات تمیز می‌مونه. برای یادگیری بیشتر در مورد اسنیپت‌های HTML، CSS و JS که می‌تونی توی پروژه‌هات ازشون استفاده کنی، به بخش اسنیپت‌های ما سر بزن.

۳. کنترل زمان‌بندی (Timing)

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

روی کد کلیک کن تا کپی بشه:


import time
from contextlib import contextmanager

@contextmanager
def timer(label):
start = time.time()
try:
yield
finally:
end = time.time()
print(f"⏲️ '{label}' در {end - start:.4f} ثانیه اجرا شد.")

with timer("عملیات سنگین"):
time.sleep(2) # شبیه‌سازی یک کار سنگین
print("کار سنگین انجام شد.")

with timer("حلقه ساده"):
total = 0
for i in range(1_000_000):
total += i
print(f"حاصل جمع: {total}")

مزایای استفاده از Context Managerها

استفاده از Context Managerها فقط یه بحث سلیقه نیست، بلکه یه سری مزایای واقعاً مهم رو برای کد شما به ارمغان میاره:

مزیت توضیح
۱. امنیت منابع تضمین می‌کند که منابع (فایل، اتصال به دیتابیس، قفل) حتی در صورت بروز خطا نیز به درستی بسته یا آزاد می‌شوند.
۲. خوانایی بالا کد را خواناتر و تمیزتر می‌کند، زیرا منطق راه‌اندازی و تمیزکاری منابع از منطق اصلی برنامه جدا می‌شود.
۳. کاهش خطاهای برنامه‌نویسی احتمال فراموش کردن بسته شدن منابع را از بین می‌برد، که یکی از دلایل رایج Bugها و مشکلات عملکردی است.
۴. قابلیت استفاده مجدد می‌توانید Context Managerهای خود را بسازید و بارها در بخش‌های مختلف پروژه یا حتی پروژه‌های دیگر استفاده کنید.

عیب‌یابی سریع: مشکلات رایج و راه‌حل‌ها

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

۱. فراموش کردن مقدار بازگشتی __enter__

اگه متد __enter__ هیچ مقداری برنگردونه یا None برگردونه، و شما از as برای گرفتن یه متغیر استفاده کنی، اون متغیر None خواهد بود.

راه‌حل:

مطمئن شو که متد __enter__ مقداری رو که می‌خوای در بلوک with ازش استفاده کنی (مثلاً آبجکت فایل یا اتصال دیتابیس)، return می‌کنه. در Context Managerهای تابع‌محور، این کار با yield انجام میشه.

۲. مدیریت نادرست خطا در __exit__

اگه متد __exit__ (یا کد بعد از yield در @contextmanager) یه خطای جدید ایجاد کنه یا True برگردونه در حالی که خطا واقعاً هندل نشده، ممکنه با رفتارهای غیرمنتظره روبه‌رو بشی.

راه‌حل:

متد __exit__ فقط در صورتی True برگردونه که خطا رو به طور کامل هندل کرده و نمی‌خوای دوباره منتشر بشه. در بیشتر موارد، بهتره False برگردونی تا خطا (در صورت وجود) به بیرون بلوک with منتقل بشه و توسط مکانیزم‌های try...except بیرونی مدیریت بشه.

۳. استفاده از with برای آبجکت‌های غیر Context Manager

اگه سعی کنی از with برای آبجکتی استفاده کنی که متدهای __enter__ و __exit__ رو نداره، با خطای AttributeError روبه‌رو میشی.

راه‌حل:

همیشه مطمئن شو که آبجکتی که به with میدی، واقعاً یک Context Manager است (یا حداقل اینترفیس لازم را پیاده‌سازی کرده باشد). اگر لازم است، خودت یک Context Manager سفارشی برای آن آبجکت بنویس.

۴. نشت منابع در صورت خطا در __enter__

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

راه‌حل:

منطق راه‌اندازی منبع را تا حد امکان ساده و بدون خطا نگه دار. اگر راه‌اندازی پیچیده است و احتمال خطا دارد، می‌توانید از یک try...finally داخلی در متد __enter__ استفاده کنید تا منابعی که تا آن لحظه باز شده‌اند، بسته شوند.

نتیجه‌گیری

خب رفیق، دیگه با Context Managerها و دستور with حسابی آشنات کردم! این ابزار پایتون واقعاً یه نجات‌دهنده‌ است برای هر برنامه‌نویسی که می‌خواد کد امن‌تر، خواناتر و حرفه‌ای‌تری بنویسه. از فایل‌ها و دیتابیس‌ها گرفته تا قفل‌ها و منابع سفارشی خودت، Context Managerها یه راه حل عالی برای مدیریت منابع با کمترین دردسر هستن. همین امروز شروع کن و ازشون توی پروژه‌هات استفاده کن تا تفاوت رو احساس کنی. اگر در حوزه‌های دیگه مثل جاوااسکریپت، CSS یا HTML هم نیاز به اسنیپت‌های آماده یا راهنمایی داری، حتماً به بخش اسنیپت‌های ما سر بزن. کدنویسی عالی داشته باشی!

❓ سوالات متداول (FAQ)

Q: Context Manager چیه؟

A: Context Manager یک پروتکل توی پایتونه که به آبجکت‌ها اجازه میده منابع رو به درستی مدیریت کنن. این کار با پیاده‌سازی متدهای خاص __enter__ برای راه‌اندازی و __exit__ برای تمیزکاری منبع انجام میشه.

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

A: دستور with تضمین می‌کنه که متد __enter__ در ابتدای بلوک و متد __exit__ در انتهای بلوک (چه به صورت عادی، چه به خاطر خطا) فراخونی بشه. این باعث میشه منابع به صورت خودکار و امن مدیریت بشن.

Q: آیا می‌تونم Context Manager خودم رو بسازم؟

A: بله، قطعاً! می‌تونی با تعریف یک کلاس که متدهای __enter__ و __exit__ رو پیاده‌سازی می‌کنه، Context Manager سفارشی بسازی. همچنین با استفاده از دکوراتور @contextmanager از ماژول contextlib می‌تونی به صورت تابع‌محور هم این کار رو انجام بدی.

Q: کاربرد اصلی Context Managerها چیه؟

A: کاربرد اصلیشون مدیریت خودکار و امن منابع سیستم مثل فایل‌ها، اتصالات دیتابیس، قفل‌ها در برنامه‌نویسی موازی، و دایرکتوری‌های موقت هست. این کار باعث جلوگیری از نشت منابع و افزایش پایداری و خوانایی کد میشه.

Table of Contents

آخرین نوشته‌ها