مستوى القارئ: متوسط. المقال ده مكتوب لمن بيكتب Python بانتظام ويعرف try/except/finally ومحتاج يكسب نظافة كود و resource safety. لو لسه مبتدئ، ابدأ من قسم "الـ Context Manager بمثال حارس الباب" وارجع لباقي الأقسام بعد كده.
لو الـ service بتاعتك بتفتح ملف أو PostgreSQL connection في 22 مكان مختلف، وكل مكان معمول بـ try/finally، انت بتعيد كتابة 6 سطور boilerplate في كل مرة. وكفاية تنسى finally في مكان واحد عشان السيرفر يبدأ ياكل file descriptors لحد ما يقف.
المشكلة باختصار
عندك دالة بتفتح ملف، تكتب فيه، تقفله. لو exception حصل بين الفتح والقفل، الملف بيفضل مفتوح في الذاكرة لحد ما الـ garbage collector يلاقيه. على سيرفر بيخدم 8,400 طلب/دقيقة، ده بيتراكم في ثواني.
الافتراض هنا إنك بتشتغل على CPython 3.10+ وبتستخدم مكتبات بتدعم الـ context manager protocol (وده الحال في 95% من المكتبات الشائعة: open()، psycopg2، requests.Session، threading.Lock، tempfile.TemporaryDirectory).
الـ Context Manager بمثال حارس الباب
تخيّل غرفة فيها سيرفر مهم. أي حد يدخل لازم يقفل الباب لما يخرج، وإلا أي حد تاني يقدر يدخل ويعبث. كل مرة تنسى تقفل، المشكلة بتكبر.
الحل البشري: تحط حارس عند الباب. لما تدخل، الحارس بيفتح. لما تخرج — بأي طريقة، حتى لو خرجت جري بسبب إنذار — الحارس بيقفل ورائك. مش هتفتكر، مش هتنسى. مسؤوليته مش مسؤوليتك.
الـ context manager في Python هو الحارس ده بالظبط. أي object فيه method اسمها __enter__ و method اسمها __exit__ يبقى context manager. والكلمة with هي اللي بتقول للـ Python "نادي الحارس ده".
التعريف العلمي الدقيق
الـ Context Manager Protocol موصوف رسمياً في PEP 343 (Guido van Rossum و Nick Coghlan، 2005). البيان with EXPR as VAR: بيتـ desugar تحت الكابوت لما يلي:
manager = EXPR
exit = type(manager).__exit__
value = type(manager).__enter__(manager)
exc = True
try:
try:
VAR = value
BLOCK
except:
exc = False
if not exit(manager, *sys.exc_info()):
raise
finally:
if exc:
exit(manager, None, None, None)
الـ الضمانة هنا إن بيتنادى دايماً: في الحالة العادية، في الحالة اللي فيها exception، حتى لو في أو جوّه الـ block. ده اللي بيخلّيه أأمن من اليدوي — لأنه impossible تنساه.