with – מנהל הקשר (context manager)
- Feb 2, 2019
- 2 min read
with – מנהל הקשר (context manager)
בשיעור על ניהול קבצים למדנו איך לפתוח קבצים עם open ולסגור אותם עם close. הבעיה היא שאם קורית שגיאה באמצע, הקובץ עלול להישאר פתוח ולגרום לבעיות. הפתרון האלגנטי של פייתון לזה הוא פקודת with.
בואו נראה קודם את הדרך הישנה ואז את הדרך עם with –
# הדרך הישנה
f = open("myfile.txt", "w")
f.write("שלום עולם")
f.close()
# הדרך עם with
with open("myfile.txt", "w") as f:
f.write("שלום עולם")
בגרסה עם with, הקובץ נסגר אוטומטית ברגע שיוצאים מהבלוק – גם אם קרתה שגיאה באמצע. אין צורך לקרוא ל-close בעצמנו. המילה as נותנת לנו שם למשתנה שמייצג את הקובץ הפתוח.
למה זה חשוב? בגלל שגיאות. תראו מה קורה בלי with –
f = open("data.txt", "r") content = f.read() result = int(content) # שגיאה אם הקובץ לא מכיל מספר! f.close() # השורה הזו לא תתבצע אם יש שגיאה למעלה
אם השורה השלישית זורקת שגיאה, close לעולם לא ייקרא והקובץ יישאר פתוח. עם with הבעיה הזאת לא קיימת –
with open("data.txt", "r") as f: content = f.read() result = int(content) # גם אם יש שגיאה כאן # הקובץ נסגר אוטומטית, תמיד
אפשר גם לפתוח כמה קבצים בו-זמנית –
with open("input.txt", "r") as fin, open("output.txt", "w") as fout: for line in fin: fout.write(line.upper())
בדוגמה למעלה פתחנו קובץ אחד לקריאה ואחד לכתיבה, קראנו שורה-שורה מהראשון וכתבנו לשני באותיות גדולות. שני הקבצים נסגרים אוטומטית בסוף.
איך with עובד מאחורי הקלעים? כל אובייקט שתומך ב-with צריך לממש שתי מתודות מיוחדות – __enter__ ו-__exit__. המתודה __enter__ רצה כשנכנים לבלוק, ו-__exit__ רצה כשיוצאים ממנו (גם אם יצאנו בגלל שגיאה). בואו ניצור מנהל הקשר משלנו –
class Timer: def __enter__(self): import time self.start = time.time() print("מתחילים למדוד זמן...") return self def __exit__(self, exc_type, exc_val, exc_tb): import time elapsed = time.time() - self.start print(f"עברו {elapsed:.4f} שניות") return False
with Timer(): total = sum(range(1000000))
>>>
מתחילים למדוד זמן...
עברו 0.0312 שניות
יצרנו מחלקה Timer שמודדת כמה זמן עבר בתוך בלוק with. כש-with מתחיל, __enter__ שומר את הזמן הנוכחי. כש-with נגמר, __exit__ מחשב כמה זמן עבר ומדפיס. ה-return False ב-__exit__ אומר שאם הייתה שגיאה, שתמשיך להתפשט (אם היינו מחזירים True היא הייתה נבלעת).
אפשר ליצור מנהלי הקשר גם בדרך קצרה יותר עם הדקורטור contextmanager מספריית contextlib –
from contextlib import contextmanager @contextmanager def loud_block(name): print(f"נכנסים ל-{name}") yield print(f"יוצאים מ-{name}") with loud_block("חישוב"): print("עובדים...")
>>>
נכנסים ל-חישוב
עובדים...
יוצאים מ-חישוב
כל מה שלפני yield הוא ה-__enter__ וכל מה שאחרי yield הוא ה-__exit__. דרך קומפקטית ונוחה במיוחד למקרים פשוטים.
לסיכום – פקודת with מבטיחה שמשאבים (קבצים, חיבורים, נעילות) ייסגרו תמיד, גם אם קורית שגיאה. זה הופך את הקוד לבטוח יותר ונקי יותר. בכל פעם שפותחים קובץ – להשתמש ב-with.
Comments