פעולות קבצים בפייתון

פעולות קבצים בפייתון

מאמר זה מסביר פעולות קבצים בפייתון.

עם "בטיחות", "יעילות" ו"קריאות" בראש, מדריך זה מסביר מושגים שלב אחר שלב, מהבסיס ועד לטכניקות מעשיות.

YouTube Video

פעולות קבצים בפייתון

פעולות קבצים הן מיומנות בסיסית חיונית, מסקריפטים קטנים ועד יישומים רחבי היקף.

פתיחה וסגירה של קבצים

ראשית, נבחן דוגמאות לקריאה וכתיבה של קבצי טקסט. שימוש ב'הצהרת with' (מנהל הקשר) מבטיח שהקובץ ייסגר כראוי.

הקוד הבא פותח קובץ טקסט, קורא את תוכנו ומעבד אותו שורה אחר שורה.

1# Open and read a text file safely using a context manager.
2with open("example.txt", "r", encoding="utf-8") as f:
3    # Read all lines lazily (as an iterator) and process each line.
4    for line in f:
5        print(line.rstrip())
  • הגדרה מפורשת של encoding="utf-8" מסייעת להקטין בעיות התלויות בפלטפורמה.
  • גם בקבצים גדולים, שימוש ב-for line in f חסכוני בזיכרון.

כתיבה (שכתוב ותוספת)

שימו לב למצב (mode) בעת כתיבה לקבצים. w הוא לשכתוב, בעוד ש-a הוא להוספה. השתמשו ב-with גם בעת כתיבה.

הקוד הבא מציג דוגמאות בסיסיות לשכתוב והוספה.

1# Write (overwrite) to a file.
2with open("output.txt", "w", encoding="utf-8") as f:
3    f.write("First line\n")
4    f.write("Second line\n")
5
6# Append to the same file.
7with open("output.txt", "a", encoding="utf-8") as f:
8    f.write("Appended line\n")
  • קוד זה כותב טקסט ל-output.txt ואז מוסיף טקסט נוסף לאותו קובץ. מצב 'w' משכתב את הקובץ, בעוד שמצב 'a' מוסיף שורה חדשה לסוף התוכן הקיים.
  • אם יש צורך לבצע flush, קראו ל-flush(), אך בדרך כלל התוכן נשטף אוטומטית בסיום ה-context.
  • אם מספר תהליכים או תרים עשויים לכתוב בו-זמנית, דרוש לשקול בקרה בלעדית, כגון נעילת קובץ.

קריאה וכתיבה של נתונים בינאריים

קבצים דחוסים ותמונות מטופלים במצב בינארי (rb או wb). בשונה ממצב טקסט, כאן התעלמות מ-encoding.

הקוד הבא קורא קובץ בינארי ומעתיק אותו לקובץ אחר במצב בינארי.

1# Read and write binary files.
2with open("image.png", "rb") as src:
3    data = src.read()
4
5with open("copy.png", "wb") as dst:
6    dst.write(data)
  • בעת טיפול בקבצים בינאריים גדולים, יש להימנע מקריאה בבת אחת עם read(); קריאה וכתיבה במקטעים (chunks) יעילה יותר בזיכרון.

דוגמה לטיפול בקבצים גדולים במקטעים

לקבצים ענקיים שלא נכנסים לזיכרון, יש לקרוא ולכתוב אותם במקטעים בגודל קבוע. מכיוון שזו פעולה התלויה ב-I/O, התאמת גודל הבופר עשויה להועיל.

הקוד הבא מעתיק קובץ במקטעים של 64KB. הפעולה מתבצעת במהירות עם צריכת זיכרון נמוכה.

1# Copy a large file in chunks to avoid using too much memory.
2CHUNK_SIZE = 64 * 1024  # 64 KB
3
4with open("large_source.bin", "rb") as src,      open("large_dest.bin", "wb") as dst:
5    while True:
6        chunk = src.read(CHUNK_SIZE)
7        if not chunk:
8            break
9        dst.write(chunk)
  • אפשר לכוון את גודל המקטע לפי מאפייני הרשת או הדיסק שלך. ב-SSD לרוב גודל מקטע מעט גדול יותר עובד טוב יותר.

דוגמה לשימוש בארגומנט buffering

על ידי ציון הארגומנט buffering בפונקציה open(), ניתן לשלוט בגודל המאגר שבו פייתון משתמשת באופן פנימי. דבר זה מאפשר לייעל עוד יותר את יעילות הקלט/פלט.

 1# Explicitly set the internal buffer size to 128 KB for faster I/O.
 2CHUNK_SIZE = 64 * 1024
 3BUFFER_SIZE = 128 * 1024  # 128 KB internal buffer
 4
 5with open("large_source.bin", "rb", buffering=BUFFER_SIZE) as src,      open("large_dest.bin", "wb", buffering=BUFFER_SIZE) as dst:
 6    while True:
 7        chunk = src.read(CHUNK_SIZE)
 8        if not chunk:
 9            break
10        dst.write(chunk)
  • אם מגדירים את הערך של buffering ל-0, פעולות הקלט/פלט מתבצעות ללא אגירה; 1 מפעיל אגירת שורות, וערכים של 2 או יותר משתמשים במאגר בגודל הבתים שצויין.
  • באופן כללי, הערך המוגדר כברירת מחדל מספיק, כיוון שמערכת ההפעלה מטפלת באופן יעיל בזיכרון המטמון, אך התאמת פרמטר זה יכולה להיות יעילה עבור קבצים גדולים מאוד או התקנים מיוחדים.

פעולות קבצים מודרניות באמצעות Pathlib

מודול pathlib הסטנדרטי הופך את הטיפול בנתיבים לאינטואיטיבי יותר. זה משפר את הקריאות והבטיחות בהשוואה לנתיבי מחרוזות רגילים.

 1from pathlib import Path
 2
 3path = Path("data") / "notes.txt"
 4
 5# Ensure parent directory exists.
 6path.parent.mkdir(parents=True, exist_ok=True)
 7
 8# Write and read using pathlib.
 9path.write_text("Example content\n", encoding="utf-8")
10content = path.read_text(encoding="utf-8")
11print(content)
  • קוד זה מדגים יצירת תיקייה עם pathlib, כתיבה לקובץ טקסט וקריאת תכולתו. באמצעות אובייקט Path אפשר לטפל בנתיבים בצורה אינטואיטיבית ובטוחה.
  • ל-Path יש APIs נוחים כמו iterdir() ו-glob(). אפשר לכתוב קוד ללא דאגה למפרידי נתיב שונים בין מערכות הפעלה.

קבצים ותיקיות זמניים (tempfile)

ניתן ליצור קבצים זמניים בצורה בטוחה עם tempfile. זה מונע מצבים תחרותיים ובעיות של התנגשות שמות.

הקוד הבא מציג דוגמה ליצירת נתונים זמניים באמצעות קובץ זמני.

 1import tempfile
 2
 3# Create a temporary file that is deleted on close.
 4with tempfile.NamedTemporaryFile(
 5    mode="w+",
 6    encoding="utf-8",
 7    delete=True
 8) as tmp:
 9    tmp.write("temporary data\n")
10    tmp.seek(0)
11    print(tmp.read())
12
13# tmp is deleted here
  • הקוד יוצר קובץ זמני, כותב וקורא נתונים, ומוחק אותו אוטומטית כאשר בלוק ה-with מסתיים. בשימוש ב-tempfile.NamedTemporaryFile אפשר לטפל בקבצים זמניים בצורה בטוחה וללא התנגשויות. כיוון ש-delete=True מוגדר, הקובץ נמחק אוטומטית.
  • ב-Windows, ייתכן שלא תוכל לפתוח את הקובץ מידית מידית אחרת, לכן אפשר להגדיר delete=False ולנהל את המחיקה בעצמך.

shutil: פעולות עיליות להעתקה, העברה ומחיקה

העתקה, העברה ומחיקה רקורסיבית של קבצים ותיקיות קלה עם shutil.

 1import shutil
 2
 3# Copy a file preserving metadata.
 4shutil.copy2("source.txt", "dest.txt")
 5
 6# Move a file or directory.
 7shutil.move("old_location", "new_location")
 8
 9# Remove an entire directory tree (use with caution).
10shutil.rmtree("some_directory")
  • shutil.copy2 גם מעתיק מידע נוסף (מטאדטה), כמו זמן שינוי. move ינסה להעביר את הקובץ גם אם לא ניתן להשתמש ב-rename.
  • rmtree היא פעולה מסוכנת, לכן תמיד יש לוודא ולבצע גיבוי לפני מחיקה.

מטאדטה של קובץ (os.stat) וטיפול בהרשאות

גודל הקובץ, זמן השינוי והרשאות ניתנים לקריאה ושינוי באמצעות os ו-stat.

הקוד הבא משיג מידע בסיסי על קובץ באמצעות os.stat ומשנה הרשאות באמצעות os.chmod.

 1import os
 2import stat
 3from datetime import datetime
 4
 5st = os.stat("example.txt")
 6print("size:", st.st_size)
 7print("modified:", datetime.fromtimestamp(st.st_mtime))
 8
 9# Make file read-only for owner.
10os.chmod("example.txt", stat.S_IREAD)
  • הרשאות פועלות באופן שונה בין מערכות POSIX ו-Windows. אם יש חשיבות לתאימות בין פלטפורמות, השתמש ב-API עילי או הוסף טיפול מותנה.

נעילת קובץ (בקרה בלעדית) — הבדלים בין יוניקס ל-Windows

בקרה בלעדית דרושה כאשר כמה תהליכים ניגשים לאותו קובץ במקביל. יוניקס משתמשת ב-fcntl, ו-Windows ב-msvcrt.

הקוד הבא משתמש ב-fcntl.flock במערכות UNIX כדי להשיג נעילה בלעדית בעת כתיבה.

 1# Unix-style file locking example
 2import fcntl
 3
 4with open("output.txt", "a+", encoding="utf-8") as f:
 5    fcntl.flock(f, fcntl.LOCK_EX)
 6    try:
 7        f.write("Safe write\n")
 8        f.flush()
 9    finally:
10        fcntl.flock(f, fcntl.LOCK_UN)
  • קוד זה משיג נעילה בלעדית באמצעות fcntl.flock במערכות דמויות UNIX לכתיבה בטוחה ומניעת כתיבות במקביל לאותו קובץ. תמיד יש לשחרר את הנעילה לאחר העיבוד כדי לאפשר גישה מתהליכים אחרים.
  • ב-Windows, השתמש ב-msvcrt.locking(). לשימוש ברמה גבוהה יותר, שקול ספריות נוספות כמו portalocker או filelock.

דפוסי כתיבה אטומיים לקבצים

כדי למנוע פגום בקובץ בזמן עדכון, כתוב לקובץ זמני ולאחר הצלחה החלף אותו עם os.replace.

כתיבה לקובץ זמני ואז החלפה מונעת פגימה אם מתרחש קריסה.

 1import os
 2from pathlib import Path
 3import tempfile
 4
 5def atomic_write(path: Path, data: str, encoding="utf-8"):
 6    # Write to a temp file in the same directory and atomically replace.
 7    dirpath = path.parent
 8    with tempfile.NamedTemporaryFile(
 9        mode="w",
10        encoding=encoding,
11        dir=str(dirpath),
12        delete=False
13    ) as tmp:
14        tmp.write(data)
15        tmp.flush()
16        tempname = tmp.name
17
18    # Atomic replacement (on most OSes)
19    os.replace(tempname, str(path))
20
21# Usage
22atomic_write(Path("config.json"), '{"key":"value"}\n')

os.replace מבצע החלפה אטומית בתוך אותה מערכת קבצים. שים לב שאטומיות אינה מובטחת בין שיגורי קבצים שונים.

גישה מהירה באמצעות mmap (לנתונים גדולים)

לגישה אקראית לקבצים גדולים, mmap משפר את ביצועי ה-I/O. זה עוסק בעיקר בפעולות בינאריות.

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

1import mmap
2
3with open("data.bin", "r+b") as f:
4    mm = mmap.mmap(f.fileno(), 0)  # map entire file
5    try:
6        print(mm[:20])  # first 20 bytes
7        mm[0:4] = b"\x00\x01\x02\x03"  # modify bytes
8    finally:
9        mm.close()
  • קוד זה ממפה קובץ בינארי לזיכרון עם mmap ומבצע קריאה/כתיבה ישירה ברמת בתים. מיפוי זיכרון מאפשר גישה אקראית מהירה למאגרי נתונים גדולים.
  • mmap יעיל, אך שימוש לא נכון עלול לגרום לבעיות עקביות בנתונים. קרא ל-flush() לסנכרון לפי הצורך.

CSV / JSON / Pickle: קריאה וכתיבה לפי פורמט

לפורמטי נתונים ייעודיים יש מודולים משלהם. השתמש ב-csv לקבצי CSV, ב-json ל-JSON וב-pickle לשמירת אובייקטי פייתון.

הקוד הבא נותן דוגמאות בסיסיות לקריאה וכתיבה של CSV ו-JSON, ושימוש ב-Pickle. מכיוון ש-Pickle יכול להריץ קוד שרירותי, הימנע מטעינת נתונים ממקורות לא מהימנים.

 1import csv
 2import json
 3import pickle
 4
 5# CSV write
 6with open("rows.csv", "w", encoding="utf-8", newline="") as f:
 7    writer = csv.writer(f)
 8    writer.writerow(["name", "age"])
 9    writer.writerow(["Alice", 30])
10
11# JSON write
12data = {"items": [1, 2, 3]}
13with open("data.json", "w", encoding="utf-8") as f:
14    json.dump(data, f, ensure_ascii=False, indent=2)
15
16# Pickle write (only for trusted environments)
17obj = {"key": "value"}
18with open("obj.pkl", "wb") as f:
19    pickle.dump(obj, f)
  • הגדרת newline="" ל-CSV מומלצת כדי למנוע שורות ריקות נוספות ב-Windows. עם ensure_ascii=False, JSON שומר על תווים בעברית ו-UTF-8 קריאים.

קריאה וכתיבה ישירה של קבצים דחוסים (gzip / bz2 / zipfile)

הטיפול הישיר ב-gzip ו-zip יכול לחסוך מקום בדיסק. הספריה התקנית כוללת מודולים תואמים.

הקוד הבא הוא דוגמה פשוטה לקריאה וכתיבה של קבצי gzip כטקסט.

1import gzip
2
3# Write gzipped text.
4with gzip.open("text.gz", "wt", encoding="utf-8") as f:
5    f.write("Compressed content\n")
6
7# Read gzipped text.
8with gzip.open("text.gz", "rt", encoding="utf-8") as f:
9    print(f.read())
  • יש פשרה בין יחס הדחיסה למהירות בהתאם לרמת וסוג הדחיסה.

אמצעי אבטחה והתמודדות עם פגיעויות

הנקודות הבאות ניתנות לשיקול לגבי אמצעי אבטחה והתמודדות עם פגיעויות.

  • אל תשתמש בקלט לא מהימן ישירות כשמות או נתיבי קבצים.
  • השתמש ב-Pickle רק עם מקורות מהימנים.
  • מזער הרשאות הרצה ותן לתהליכים המטפלים בקבצים רק הרשאות מינימליות.
  • השתמש ב-קבצים זמניים עם tempfile, ואל תאחסן קבצים רגילים בתיקיות ציבוריות.

אם משתמשים בקלט ממשתמש בנתיבי קבצים, נדרש נרמול ואימות. לדוגמה, השתמש ב-Path.resolve() ובדוק את תיקיות האב.

 1from pathlib import Path
 2
 3def safe_path(base_dir: Path, user_path: str) -> Path:
 4    candidate = (base_dir / user_path).resolve()
 5    if base_dir.resolve() not in candidate.parents and base_dir.resolve() != candidate:
 6        raise ValueError("Invalid path")
 7    return candidate
 8
 9# Usage
10base = Path("/srv/app/data")
11
12# Raises an exception: attempted path traversal outside `base`
13safe = safe_path(base, '../etc/passwd')
  • יש לנקוט משנה זהירות בעת שימוש בקלט חיצוני כנתיב קובץ באפליקציות רשת או API ציבורי.

סיכום דפוסי עבודה נפוצים

  • תמיד השתמש ב'הצהרת with' (סגירה אוטומטית).
  • הגדר במפורש encoding לנתוני טקסט.
  • קרא וכתוב קבצים גדולים במקטעים.
  • יש להכניס מנגנון נעילת קבצים עבור משאבים משותפים.
  • בעדכונים קריטיים, השתמשו בתבנית האטומית של 'כתיבה לקובץ זמני → os.replace'.
  • יש תמיד לאשר וליצור גיבוי לפני ביצוע פעולות מסוכנות (כגון מחיקה או שכתוב).
  • בצעו נירמול ואימות בעת שימוש בקלט חיצוני כנתיב קובץ.

סיכום

בעבודה עם קבצים, חשוב להשתמש בשיטות בטוחות ואמינות כמו שימוש ב-with, ציון מפורש של קידוד, וכתיבה אטומית. לעיבוד רחב היקף או גישה במקביל, יש ליישם מנגנוני נעילה וניהול לוגים כדי למנוע פגיעה בנתונים והתנגשויות. איזון בין יעילות לבטיחות הוא המפתח לתפעול קבצים אמין.

תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.

YouTube Video