פעולות קבצים בפייתון
מאמר זה מסביר פעולות קבצים בפייתון.
עם "בטיחות", "יעילות" ו"קריאות" בראש, מדריך זה מסביר מושגים שלב אחר שלב, מהבסיס ועד לטכניקות מעשיות.
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 בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.