لو عندك سكربت بيفتح 500 ملف في الدقيقة من غير with، بعد ساعتين هتلاقي السيرفر بيرمي OSError: Too many open files. الحل مش شيل الكود وإعادة كتابته، الحل سطر واحد بيضيف with ويخلّي بايثون تقفل الموارد بدلًا منك حتى لو حصل استثناء.
Python Context Managers: كلمة with اللي بتقفل الموارد بدلًا منك
المشكلة باختصار
في بايثون، كل مورد خارج الذاكرة — ملف، socket، اتصال PostgreSQL، lock على thread، file lock على القرص — بيحتاج خطوتين: فتح واستخدام وتنظيف. لو نسيت التنظيف، الـ OS هيفضل محتفظ بالمورد حتى لو الكود خلص. ده اسمه resource leak، وهو من أشهر أسباب سقوط السيرفرات في production.
الطريقة الشائعة الغلط: try/finally يدوي في كل مكان. المشكلة إن أي developer هينسى الـ finally مرة واحدة بس، والمشكلة مش هتظهر في الـ dev لأن الذاكرة والـ file descriptors بتوسع. هتظهر في production على 50K طلب/ساعة.
اشرحها لو عندي 7 سنين
تخيل إنك داخل ملهى ألعاب ومعاك تذكرة. في بواب بيفتحلك الباب لمّا تدخل، وبواب تاني بيقفل الباب لمّا تخرج. انت مش مسؤول عن الأبواب، انت بس بتلعب جوه. لو انت اللي كنت هتقفل الباب بإيدك، ممكن تنسى، والـ ملهى يفضل مفتوح طول الليل.
الـ with في بايثون هو زي بواب الدخول والخروج ده بالظبط. انت بتقول "أنا داخل أتعامل مع الملف"، وبايثون بتتكفل بفتحه قبل ما تبدأ، وقفله بعد ما تخلّص — حتى لو حصل حريق (استثناء) جوه.
الفكرة العلمية: بروتوكول اسمه Context Manager
كل object قابل للاستخدام مع with بيحقق بروتوكول من method اتنين:
__enter__(self): بتترجّع القيمة اللي هتتحط بعدas. هنا بيحصل التهيئة (فتح الملف، بدء الـ transaction، أخذ الـ lock).__exit__(self, exc_type, exc_value, traceback): بتتنادى دايمًا لمّا تخرج من الكتلة — سواء بنجاح أو باستثناء. هنا بيحصل التنظيف.
لو __exit__ رجّع True، الاستثناء بيتبلع. لو رجّع False أو None، الاستثناء بيكمّل طريقه لأعلى. الافتراض الشائع: ابقَ مع None إلا لو عندك سبب واضح لإخفاء الاستثناء.
مثال تنفيذي: قراءة ملف بالطريقتين
الطريقة اليدوية المعرّضة للنسيان:
f = open("users.csv", "r", encoding="utf-8")
try:
data = f.read()
process(data)
finally:
f.close()
الطريقة باستخدام with: