Menu

בדיקה אם קובץ קיים ב-Python: os.path, pathlib ו-try/except

שלוש דרכים לבדוק אם קובץ קיים ב-Python: os.path.exists ו-isfile, המתודות Path.exists ו-is_file של pathlib, ופתיחת הקובץ בתוך try/except FileNotFoundError.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

התשובה הקצרה

כדי לבדוק אם קובץ קיים, השתמשו ב-os.path.isfile() מהספרייה הסטנדרטית. היא מחזירה True כשהנתיב מצביע על קובץ רגיל שקיים, ו-False בכל מקרה אחר. היא אף פעם לא זורקת שגיאה על נתיב חסר.

נתיבים הם יחסיים לתיקייה שבה התוכנית רצה (תיקיית העבודה הנוכחית), אלא אם מעבירים נתיב מוחלט כמו /home/ada/notes.txt או C:\Users\Ada\notes.txt.

שאר העמוד מכסה את הדרכים האחרות לשאול את השאלה, במה הן שונות, ואת המקרה שבו לא כדאי לשאול אותה בכלל.

os.path.exists מול isfile מול isdir

במודול os.path יש שלוש בדיקות קשורות, והן עונות על שאלות שונות במקצת:

פונקציהמחזירה True כשהנתיב הוא
os.path.exists(p)כל דבר שקיים: קובץ, תיקייה או symlink תקין
os.path.isfile(p)קובץ רגיל שקיים
os.path.isdir(p)תיקייה שקיימת

ההבדל מתגלה ברגע שמעורבת תיקייה:

פלט:

reports True False True
reports/summary.txt True True False
reports/old.txt False False False

os.path.exists("reports") מחזירה True למרות ש-reports היא תיקייה. אם הקוד שלכם עומד לקרוא את הנתיב כקובץ, exists היא הבדיקה הלא נכונה: תיקייה תעבור אותה, ואז open("reports") תיכשל (עם IsADirectoryError ב-Linux וב-macOS, ו-PermissionError ב-Windows). בחרו ב-isfile כשאתם מתכוונים לקובץ וב-isdir כשאתם מתכוונים לתיקייה.

עוד שני פרטים על שלוש הפונקציות האלה של os.path:

  • הן עוקבות אחרי symlink. קישור שמצביע על קובץ אמיתי נחשב קובץ; קישור שבור (היעד שלו נעלם) מחזיר False בכל שלוש הבדיקות.
  • אם ל-Python אין הרשאה להסתכל בתוך תיקייה, הבדיקה מחזירה False במקום לזרוק PermissionError. False פירושו "לא נמצא או לא נגיש", לא בהכרח "לא קיים".

בדיקה עם pathlib

pathlib הוא ה-API מונחה העצמים לנתיבים בספרייה הסטנדרטית, ורוב הקוד החדש משתמש בו. לאובייקט Path יש את אותן שלוש בדיקות בתור מתודות:

Path.exists(), Path.is_file() ו-Path.is_dir() מתנהגות כמו os.path.exists, isfile ו-isdir. האופרטור / מחבר חלקי נתיב עם המפריד הנכון למערכת ההפעלה, כך ש-folder / "summary.txt" עובד ב-Windows, ב-macOS וב-Linux באותה מידה. ראו טיפול בקבצים לקריאה וכתיבה דרך אובייקטי Path.

באיזו מהן להשתמש? אם שאר הקוד שלכם כבר מעביר אובייקטי Path, הישארו עם pathlib. אם אתם עובדים עם מחרוזות רגילות ופונקציות של os, os.path.isfile נכונה בדיוק באותה מידה. הן נותנות את אותן תשובות.

למה בדיקה מוקדמת יכולה להיות צעד שגוי

הסיבה הנפוצה ביותר לבדוק אם קובץ קיים היא כדי לפתוח אותו מיד אחר כך:

import os

if os.path.isfile("config.txt"):
    with open("config.txt") as f:      # ייתכן שהקובץ כבר נעלם
        settings = f.read()
else:
    settings = ""

יש כאן פער. בין הקריאה ל-isfile() לבין הקריאה ל-open(), תוכנית אחרת, thread אחר או המשתמש יכולים למחוק את הקובץ או לשנות את שמו. הבדיקה אמרה True, הפתיחה בכל זאת נכשלת, והתוכנית קורסת עם FileNotFoundError בכל מקרה. לפער הזה קוראים race condition, או "time of check to time of use". זה נדיר על המחשב הנייד שלכם והרבה פחות נדיר על שרת עמוס שבו כמה תהליכים חולקים תיקייה.

הפתרון הוא לוותר על הבדיקה הנפרדת ולתת ל-open() להיות הבדיקה. Python זורקת FileNotFoundError כשהקובץ חסר, ואתם מטפלים בזה ב-except:

פלט:

theme=dark
(no settings file, using defaults)

עכשיו יש פעולה אחת במקום שתיים, כך שאין מול מה להתחרות. לסגנון הזה יש שם בקהילת Python: EAFP, "easier to ask forgiveness than permission" (קל יותר לבקש סליחה מאשר רשות). הסגנון ההפוך, לבדוק קודם, נקרא LBYL, "look before you leap" (הסתכל לפני שאתה קופץ). Python נוטה ל-EAFP בעבודה עם קבצים, כי מערכת ההפעלה היא היחידה שיודעת את התשובה ברגע שבו פותחים את הקובץ.

FileNotFoundError היא לא הדבר היחיד שיכול להשתבש. נתיב שהוא תיקייה זורק IsADirectoryError (PermissionError ב-Windows), וקובץ שאין הרשאה לקרוא אותו זורק PermissionError. שלושתן תת-מחלקות של OSError, כך ש-except OSError: תופס בבת אחת כל מקרה של "לא הצלחתי לפתוח". עמוד החריגות מסביר איך לבחור בין תפיסת שגיאה ספציפית אחת לתפיסת משפחה שלמה.

מתי בדיקה פשוטה זה בסדר

לבדוק קודם זה לא שגוי בכל מקרה. זה הכלי הנכון כשצריך רק את התשובה ולא עומדים לפעול על הקובץ בשורה הבאה:

  • לבחור איזו הודעה להציג ("נמצאו 3 משחקים שמורים")
  • לאמת נתיב שהמשתמש הקליד לפני שמתחילים משימה ארוכה
  • לדלג על עבודה שכבר נעשתה, כשתשובה לא עדכנית עולה לכל היותר בחזרה על העבודה

כלל האצבע: אם אתם עומדים לפתוח, לקרוא, לכתוב או למחוק את הקובץ, עשו את זה בתוך try. אם אתם רק רוצים לדעת, isfile() או Path.is_file() זה בסדר.

יצירת קובץ רק אם הוא לא קיים

גם השאלה ההפוכה עולה: "כתבו את הקובץ הזה, אבל לעולם אל תדרסו קובץ קיים". הגרסה המתבקשת סובלת מאותו race כמו קודם:

if not os.path.exists("report.txt"):
    with open("report.txt", "w") as f:   # תהליך אחר עלול ליצור אותו קודם
        f.write("new report\n")

המצב "x" (יצירה בלעדית) עושה את שני הצעדים בבת אחת. הוא יוצר את הקובץ, או זורק FileExistsError אם השם כבר תפוס:

פלט:

attempt 1: created report.txt
attempt 2: report.txt already exists, left it alone

עבור תיקיות, os.makedirs(path, exist_ok=True) ו-Path(path).mkdir(parents=True, exist_ok=True) יוצרות את התיקייה ואת כל תיקיות האב החסרות, ולא עושות כלום אם היא כבר קיימת. אין צורך לקרוא קודם ל-isdir().

טעויות נפוצות

  • שימוש ב-exists() כשהכוונה לקובץ. תיקייה באותו שם עוברת את הבדיקה. השתמשו ב-isfile() או ב-Path.is_file().
  • שוכחים מאיפה מתחילים נתיבים יחסיים. את "data.txt" מחפשים בתיקיית העבודה הנוכחית, כלומר איפה שהפעלתם את התוכנית, ולא בהכרח בתיקייה שמכילה את הסקריפט. כדי למצוא קובץ שנמצא ליד הסקריפט, בנו את הנתיב מ-Path(__file__).parent.
  • בודקים ואז פותחים. הקובץ יכול להשתנות בינתיים. פתחו בתוך try וטפלו ב-FileNotFoundError.
  • תופסים כל חריגה. except Exception: סביב open() מסתיר גם שגיאות כתיב ובאגים בקוד שקורא את הקובץ. תפסו FileNotFoundError או OSError.

שאלות נפוצות

איך בודקים אם קובץ קיים ב-Python?

ייבאו את os וקראו ל-os.path.isfile("data.txt"), שמחזירה True רק עבור קובץ רגיל שקיים. עם pathlib זה Path("data.txt").is_file(). השתמשו ב-os.path.exists() או ב-Path.exists() כשגם תיקייה בשם הזה צריכה להיחשב.

מה ההבדל בין os.path.exists ל-os.path.isfile?

os.path.exists(p) מחזירה True עבור כל דבר שנמצא בנתיב: קובץ, תיקייה או symlink שמצביע על משהו אמיתי. os.path.isfile(p) מחזירה True רק עבור קובץ רגיל, ולכן היא מחזירה False עבור תיקייה. os.path.isdir(p) היא המקבילה לתיקיות.

האם צריך לבדוק אם קובץ קיים לפני שפותחים אותו?

בדרך כלל לא. הקובץ יכול להימחק או להיווצר בין הבדיקה לבין הקריאה ל-open(), כך שהבדיקה לא מבטיחה כלום. פתחו את הקובץ ישירות בתוך try וטפלו ב-FileNotFoundError בתוך except. בדיקת קיום נפרדת מתאימה כשצריך רק את התשובה, למשל כדי להחליט איזו הודעה להציג.

איך יוצרים קובץ רק אם הוא לא קיים?

פתחו אותו במצב "x": open("report.txt", "x"). Python יוצרת את הקובץ, או זורקת FileExistsError אם כבר יש שם משהו. הבדיקה והיצירה קורות בצעד אחד, כך שאף תוכנית אחרת לא יכולה להשתחל ביניהן.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל