הפקודה with בפייתון מספקת דרך נוחה ובטוחה להבטיח שקוד ההכנה והסגירה (setup ו-teardown) יתבצע תמיד – וזה חשוב מאוד כשמנהלים משאבים כמו קבצים, חיבורים לרשת, נעילות ועוד.
פרוטוקול מנהל הקשר כולל שתי מתודות:
- __enter__: מופעלת בכניסה לבלוק של with
- __exit__: מופעלת ביציאה מהבלוק (גם אם הייתה חריגה)
אובייקט שמממש את שתי המתודות האלה נקרא מנהל הקשר, וניתן להשתמש בו בתוך הפקודה with.
כאשר נכנסים לבלוק with, פייתון קוראת ל-__enter__, ואם יש ערך שהוא מחזיר – הוא נשמר במשתנה אחרי as (אם קיים).
כאשר יוצאים מהבלוק – בין אם זה קרה בצורה רגילה או בגלל שגיאה – פייתון קוראת ל-__exit__, אשר בדרך כלל מטפלת בשחרור המשאבים.
המנגנון הזה מבטיח שהמשאבים ישתחררו תמיד – אפילו אם התרחשה שגיאה.
דוגמה פשוטה לכך היא פתיחת קובץ.
אובייקט קובץ בפייתון הוא מנהל הקשר:
כאן, הפונקציה open() מחזירה אובייקט קובץ שיש לו את המתודות __enter__ (שמחזירה את אובייקט הקובץ עצמו) ו-__exit__ (שסוגרת את הקובץ). לכן, השימוש בתחביר with open(…) as f: הוא הדרך המקובלת והנכונה בפייתון לעבוד עם קבצים – כדי להבטיח שהם ייסגרו כראוי גם במקרה של שגיאות.
יצירת מנהלי הקשר מותאמים אישית
ניתן לממש את המתודות __enter__ ו-__exit__ גם במחלקות שאתם כותבים בעצמכם.
למשל, נניח שיש לנו אובייקט חיבור למסד נתונים, ואנחנו רוצים להשתמש במנהל הקשר כדי לבצע commit או rollback אוטומטיים לעסקאות:
בדוגמה הזו, המחלקה DatabaseConnection דואגת לכך שגם אם הבלוק מסתיים בהצלחה וגם אם מתרחשת שגיאה – העסקה במסד הנתונים תיסגר בצורה תקינה, והחיבור ייסגר.
הפרמטרים של __exit__ (exc_type, exc_val, exc_tb) מציינים האם החריגה (אם הייתה) היא זו שגרמה ליציאה מהבלוק – אם לא הייתה חריגה, שלושתם יהיו None.
אם __exit__ מחזירה True, החריגה תודחק (כלומר, תיחשב כאילו טופלה); לרוב מחזירים False כדי שהשגיאה תועבר הלאה ותגרום לחריגה רגילה.
כחלופה פשוטה יותר למקרים בסיסיים, ניתן להשתמש בדקורטור @contextmanager מתוך המודול contextlib.
הדקורטור הזה מאפשר לכתוב מנהל הקשר באמצעות תחביר של גנרטור – מחזירים ערך אחד באמצעות yield להפעלת הבלוק, ואז הקוד ממשיך לאחר ה-yield לביצוע פעולות הניקוי (cleanup).
שיטה זו לעיתים מובילה לקוד תמציתי וברור יותר.
המתודה Timer.__enter__ צריכה להפעיל טיימר בתחילת הבלוק, ו-Timer.__exit__ צריכה לעצור את הטיימר ולשמור את הזמן שחלף (ואולי גם לטפל או להתעלם מחריגות לפי הצורך).