עבודה עם קבצי טקסט: open, with, קריאה וכתיבה
- לפתוח קובץ במצב המתאים — r, w או a — ולהבין מה כל מצב עושה לתוכן הקיים
- להשתמש ב-
withכדי שהקובץ ייסגר תמיד, גם כשיש שגיאה - לקרוא קובץ כולו, שורה אחר שורה, ולכתוב ולהוסיף אליו
- לבנות נתיב לקובץ ביחס למיקום הסקריפט עם pathlib, ולטפל ב-FileNotFoundError
מה נלמד?
- פתיחת קובץ עם
openומצבי פתיחה. - למה חייבים לסגור קובץ, ואיך
withעושה את זה אוטומטית. - קריאה: כל הקובץ, שורה אחר שורה.
- כתיבה והוספה.
- נתיב עמיד, כזה שלא תלוי במקום שממנו מריצים (הבטחנו בנושא מחשב, מערכת הפעלה ורשת!).
למה צריך את זה?
נושא מחשב, מערכת הפעלה ורשת: “כל מה שב-RAM נעלם כשהתוכנית נגמרת”. קבצים הם הדרך הבסיסית לשמור מידע על ה-Storage: הגדרות, דוחות, היסטוריה, נתונים לייבוא וייצוא. הספירה שתמיד חזרה ל-1 ב-memory_test.py? היום נגרום לה לשרוד בין הרצות.
ובפרויקט של נושא OOP תשמרו את כל המידע של המערכת לקובץ.
הסבר מהבסיס
פתיחת קובץ
open(path, mode) מבקשת ממערכת ההפעלה (System Call!) גישה לקובץ, ומחזירה אובייקט קובץ שדרכו קוראים וכותבים.
מצבי פתיחה (Modes)
| מצב | שם | אם הקובץ קיים | אם לא קיים | פעולות |
|---|---|---|---|---|
"r" | Read (ברירת מחדל) | פותח לקריאה | FileNotFoundError | קריאה בלבד |
"w" | Write | מוחק את כל התוכן! | יוצר | כתיבה |
"a" | Append | שומר את התוכן, וכותב בסוף | יוצר | כתיבה לסוף |
open("data.txt", "w") מרוקן את הקובץ ברגע הפתיחה, עוד לפני שכתבתם תו אחד. אם התכוונתם להוסיף, המידע הקודם כבר נמחק. להוספה תמיד "a".
למה צריך לסגור קובץ?
- כתיבה לקובץ נאספת קודם בחוצץ (Buffer) בזיכרון, ונכתבת לדיסק בפועל רק בסגירה או כשהחוצץ מתמלא. בלי סגירה, חלק מהמידע עלול לא להישמר.
- מערכת ההפעלה מגבילה כמה קבצים תהליך יכול להחזיק פתוחים. קבצים שלא נסגרים “דולפים”.
- ב-Windows, קובץ פתוח לפעמים נעול, ותוכנות אחרות לא יכולות לגשת אליו.
with — סגירה אוטומטית
with open("notes.txt", "r") as file:
content = file.read()
# here the file is already closed - alwayswith מבטיח שהקובץ ייסגר ברגע שיוצאים מהבלוק: בסוף רגיל, ב-return, ואפילו כשנזרקת שגיאה. זוכרים את finally מנושא Exceptions ו-Logging? with עושה בדיוק את זה, בצורה קצרה. תמיד פותחים קבצים עם with.
open = לקחת ספר מהמדף. close = להחזיר אותו. בלי with, אתם צריכים לזכור להחזיר, ואם מתקשרים אליכם באמצע (שגיאה), הספר נשאר על השולחן. with = ספרנית שמחזירה את הספר בשבילכם ברגע שאתם קמים מהשולחן, לא משנה למה קמתם.
דרכי קריאה
| דרך | מחזירה | מתי |
|---|---|---|
file.read() | מחרוזת אחת עם כל הקובץ | קבצים קטנים |
file.readlines() | רשימה של שורות (כל אחת עם \n בסוף) | כשצריך את כל השורות כרשימה |
for line in file: | שורה אחר שורה | הדרך המומלצת. עובדת גם לקבצים ענקיים, כי לא טוענת הכול לזיכרון |
\n הוא תו ירידת שורה (Newline). כל שורה בקובץ מסתיימת בו. line.strip() מסירה אותו (ואת הרווחים בקצוות).
כתיבה
file.write(text) כותבת בדיוק את הטקסט, בלי לרדת שורה אוטומטית (שלא כמו print). רוצים שורה חדשה? מוסיפים "\n" בעצמכם. ו-write מקבלת רק מחרוזת: מספרים ממירים קודם עם str().
נתיב עמיד עם pathlib
בנושא מחשב, מערכת הפעלה ורשת ראינו שנתיב יחסי נמדד מהתיקייה שממנה הרצתם. הפתרון המקצועי: לבנות את הנתיב ביחס למיקום הקובץ .py עצמו.
from pathlib import Path
BASE_DIR = Path(__file__).parent # the folder this .py file is in
DATA_FILE = BASE_DIR / "data.txt" # "/" joins path parts, on every OS__file__— משתנה מובנה: הנתיב של הקובץ הנוכחי.Path(...).parent— התיקייה שמכילה אותו./ביןPathלמחרוזת — מחבר חלקי נתיב בצורה נכונה בכל מערכת הפעלה (\ב-Windows,/במק).
עכשיו DATA_FILE מצביע לאותו קובץ לא משנה מאיפה מריצים.
תחביר
from pathlib import Path
path = Path(__file__).parent / "notes.txt"
# write (creates or overwrites)
with open(path, "w", encoding="utf-8") as file:
file.write("first line\n")
# append (adds to the end)
with open(path, "a", encoding="utf-8") as file:
file.write("another line\n")
# read everything
with open(path, "r", encoding="utf-8") as file:
content = file.read()
# read line by line
with open(path, "r", encoding="utf-8") as file:
for line in file:
print(line.strip())
path.exists() # True / Falseencoding="utf-8" — תמיד מציינים. בלי זה, ב-Windows ברירת המחדל היא קידוד אחר, וטקסט בעברית (או אימוג’י) ייכתב כג’יבריש או יזרוק UnicodeDecodeError.
דוגמת קוד ראשונה
week-2/files/diary.py:
from pathlib import Path
DIARY_FILE = Path(__file__).parent / "diary.txt"
with open(DIARY_FILE, "w", encoding="utf-8") as file:
file.write("Day 1: started the course\n")
file.write("Day 2: learned the terminal\n")
with open(DIARY_FILE, "a", encoding="utf-8") as file:
file.write("Day 3: networks!\n")
with open(DIARY_FILE, "r", encoding="utf-8") as file:
content = file.read()
print(content)
print("File size in characters:", len(content))פלט:
Day 1: started the course
Day 2: learned the terminal
Day 3: networks!
File size in characters: 71ובתיקייה נוצר הקובץ diary.txt. פתחו אותו ב-VS Code.
פירוק הקוד שורה אחר שורה
| שורה | הסבר |
|---|---|
Path(__file__).parent / "diary.txt" | נתיב מלא לקובץ שליד הסקריפט. עובד מכל תיקייה. |
open(DIARY_FILE, "w", ...) | מצב כתיבה: יוצר את הקובץ, או מרוקן אותו אם קיים. |
file.write("...\n") | כותב טקסט. ה-\n יוצר ירידת שורה. |
סוף בלוק ה-with הראשון | הקובץ נסגר והתוכן נכתב לדיסק. |
open(..., "a", ...) | מצב הוספה: התוכן הקיים נשמר, והכתיבה מתבצעת בסוף. |
file.read() | כל התוכן כמחרוזת אחת. |
| שורה ריקה בפלט | הקובץ מסתיים ב-\n, ו-print מוסיף \n משלו. |
נסו: הריצו שוב. האם הקובץ גדל? לא, כי מצב "w" בתחילת הסקריפט מרוקן אותו בכל הרצה. שנו את הפתיחה הראשונה ל-"a" והריצו פעמיים. מה קורה?
דוגמה נוספת
מונה הרצות שבאמת שורד (הבעיה מנושא מחשב, מערכת הפעלה ורשת!) + עיבוד קובץ נתונים שורה אחר שורה.
week-2/files/run_counter.py:
from pathlib import Path
COUNTER_FILE = Path(__file__).parent / "counter.txt"
def read_count():
if not COUNTER_FILE.exists():
return 0
with open(COUNTER_FILE, "r", encoding="utf-8") as file:
return int(file.read().strip())
def save_count(count):
with open(COUNTER_FILE, "w", encoding="utf-8") as file:
file.write(str(count))
def main():
count = read_count() + 1
save_count(count)
print("This program has run", count, "times")
if __name__ == "__main__":
main()שלוש הרצות:
This program has run 1 times
This program has run 2 times
This program has run 3 timesעכשיו זה עובד, כי המספר נשמר על ה-Storage בין הרצות, ולא רק ב-RAM.
עיבוד קובץ ציונים. צרו week-2/files/grades.txt:
Dana,90
Avi,72
Noa,abc
Tom,85
Rina,64week-2/files/grades_report.py:
from pathlib import Path
GRADES_FILE = Path(__file__).parent / "grades.txt"
REPORT_FILE = Path(__file__).parent / "report.txt"
def load_grades(path):
grades = {}
with open(path, "r", encoding="utf-8") as file:
for line_number, line in enumerate(file, start=1):
line = line.strip()
if line == "":
continue
name, grade_text = line.split(",")
try:
grades[name] = int(grade_text)
except ValueError:
print("Skipping line", line_number, "- bad grade:", repr(grade_text))
return grades
def write_report(grades, path):
with open(path, "w", encoding="utf-8") as file:
for name, grade in grades.items():
status = "PASS" if grade >= 65 else "FAIL"
file.write(name + ": " + str(grade) + " " + status + "\n")
average = sum(grades.values()) / len(grades)
file.write("Average: " + str(round(average, 1)) + "\n")
def main():
grades = load_grades(GRADES_FILE)
write_report(grades, REPORT_FILE)
print("Report written to", REPORT_FILE.name)
if __name__ == "__main__":
main()פלט במסך:
Skipping line 3 - bad grade: 'abc'
Report written to report.txtreport.txt:
Dana: 90 PASS
Avi: 72 PASS
Tom: 85 PASS
Rina: 64 FAIL
Average: 77.8פירוק:
for line_number, line in enumerate(file, start=1)— מעבר על הקובץ שורה אחר שורה, עם מספר שורה, כדי לדווח איפה הבעיה.line.strip()— מסירה את ה-\n. בלעדיה,int("90\n")דווקא עובד, אבל השם היה מגיע עם רווחים מיותרים.if line == "": continue— מדלגים על שורות ריקות.name, grade_text = line.split(",")— פיצול לפי פסיק, ו-Unpacking לשני משתנים."PASS" if grade >= 65 else "FAIL"— ביטוי תנאי (Conditional Expression):if/elseקצר בשורה אחת, שמחזיר אחד משני ערכים.REPORT_FILE.name— רק שם הקובץ, בלי הנתיב המלא.Pathלא רק “נתיב”. יש לו מתודות שימושיות כמו.exists()ו-.name.
טעות נפוצה
הטעות 1: "w" במקום "a" — ומחיקת נתונים. ראינו למעלה. לפני כל open(..., "w"), שאלו את עצמכם: “האם אני בסדר עם זה שהתוכן הקודם יימחק?”
הטעות 2: FileNotFoundError בגלל נתיב יחסי.
with open("grades.txt") as file:עובד רק כשמריצים מאותה תיקייה. מכפתור Run ב-VS Code, או מתיקייה אחרת:
FileNotFoundError: [Errno 2] No such file or directory: 'grades.txt'הפתרון: Path(__file__).parent / "grades.txt". ולקבצים שאולי לא קיימים, לבדוק .exists() או לתפוס FileNotFoundError.
הטעות 3: לכתוב מספר עם write.
file.write(42)TypeError: write() argument must be str, not intתיקון: file.write(str(42)).
הטעות 4: לשכוח \n.
file.write("line one")
file.write("line two")בקובץ: line oneline two, בשורה אחת. write לא מוסיף ירידת שורה.
הטעות 5: לקרוא פעמיים מאותו קובץ פתוח.
with open(path, encoding="utf-8") as file:
first = file.read()
second = file.read()
print(repr(second)) # ''הקובץ “זוכר” מאיפה הוא קורא (מצביע קריאה). אחרי read() המצביע בסוף, ולכן הקריאה השנייה מחזירה מחרוזת ריקה.
תרגול
התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.
תרגיל 1: כתיבה וקריאה
כתבו לקובץ diary.txt שתי שורות: Day 1: started ו-Day 2: networks. אחר כך קראו את הקובץ והדפיסו את התוכן שלו.
פלט צפוי:
Day 1: started
Day 2: networksהצג פתרון
with open("diary.txt", "w", encoding="utf-8") as file:
file.write("Day 1: started\n")
file.write("Day 2: networks\n")
with open("diary.txt", "r", encoding="utf-8") as file:
print(file.read(), end="")Day 1: started
Day 2: networkswithסוגר את הקובץ אוטומטית, גם אם יש שגיאה.writeלא מוסיף ירידת שורה בעצמו — צריך\n.end=""— כי התוכן כבר נגמר ב-\n.
תרגיל 2: w מול a
with open("log.txt", "w", encoding="utf-8") as file:
file.write("first\n")
with open("log.txt", "w", encoding="utf-8") as file:
file.write("second\n")
with open("log.txt", "a", encoding="utf-8") as file:
file.write("third\n")מה יהיה בקובץ בסוף? הסבירו.
הצג פתרון
with open("log.txt", "w", encoding="utf-8") as file:
file.write("first\n")
with open("log.txt", "w", encoding="utf-8") as file:
file.write("second\n")
with open("log.txt", "a", encoding="utf-8") as file:
file.write("third\n")
with open("log.txt", "r", encoding="utf-8") as file:
print(file.read(), end="")second
third"w"מוחק את הקובץ ומתחיל מחדש — ולכןfirstנמחק כשנפתח ב-"w"בפעם השנייה."a"מוסיף לסוף, ולכןthirdנוסף אחריsecond.
תרגיל 3: שורה אחרי שורה
בקובץ grades.txt כל שורה היא name,grade. כתבו קוד שקורא את הקובץ שורה אחרי שורה, ומדפיס כל שם עם הציון שלו. דלגו על שורות ריקות.
צרו את הקובץ כך:
with open("grades.txt", "w", encoding="utf-8") as file:
file.write("dana,90\n\navi,75\nnoa,88\n")פלט צפוי:
dana 90
avi 75
noa 88הצג פתרון
with open("grades.txt", "w", encoding="utf-8") as file:
file.write("dana,90\n\navi,75\nnoa,88\n")
with open("grades.txt", "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if not line:
continue
name, grade_text = line.split(",")
print(name, int(grade_text))dana 90
avi 75
noa 88for line in file— עובר על הקובץ שורה אחרי שורה.strip()— מוריד את ה-\nורווחים מהקצוות.split(",")— מפרק את השורה, ו-Unpacking מכניס לשני משתנים.
תרגיל 4: קלט פגום
אותו קובץ ציונים, אבל עכשיו יש בו שורה פגומה. קראו את הקובץ: שורה שהציון בה אינו מספר — דלגו עליה והדפיסו הודעה עם מספר השורה. בסוף הדפיסו את הממוצע של הציונים התקינים.
with open("grades.txt", "w", encoding="utf-8") as file:
file.write("dana,90\navi,abc\nnoa,80\n")פלט צפוי:
line 2 skipped: avi,abc
Average: 85.0הצג פתרון
with open("grades.txt", "w", encoding="utf-8") as file:
file.write("dana,90\navi,abc\nnoa,80\n")
grades = []
with open("grades.txt", "r", encoding="utf-8") as file:
for number, line in enumerate(file, start=1):
line = line.strip()
name, grade_text = line.split(",")
try:
grades.append(int(grade_text))
except ValueError:
print("line", number, "skipped:", line)
print("Average:", sum(grades) / len(grades))line 2 skipped: avi,abc
Average: 85.0enumerate(file, start=1) נותן גם את מספר השורה. try/except מהשיעורים הקודמים מאפשר לדלג על שורה פגומה בלי לקרוס.
תרגיל 5: קובץ שלא קיים
- מה יקרה אם ננסה לפתוח לקריאה קובץ שלא קיים?
- כתבו פונקציה
load_tasks(path)שמחזירה את שורות הקובץ כרשימה, ורשימה ריקה אם הקובץ לא קיים. השתמשו ב-Path(path).exists().
print(load_tasks("todo.txt"))
with open("todo.txt", "w", encoding="utf-8") as file:
file.write("buy milk\ncall mom\n")
print(load_tasks("todo.txt"))פלט צפוי:
[]
['buy milk', 'call mom']הצג פתרון
- תיזרק
FileNotFoundError.
from pathlib import Path
def load_tasks(path):
if not Path(path).exists():
return []
tasks = []
with open(path, "r", encoding="utf-8") as file:
for line in file:
tasks.append(line.strip())
return tasks
print(load_tasks("todo.txt"))
with open("todo.txt", "w", encoding="utf-8") as file:
file.write("buy milk\ncall mom\n")
print(load_tasks("todo.txt"))[]
['buy milk', 'call mom']בודקים לפני הפתיחה אם הקובץ קיים. בהרצה הראשונה של תוכנית עוד אין קובץ — וזה מצב תקין, לא שגיאה.
סיכום
open(path, mode, encoding="utf-8"):"r"קריאה (ברירת מחדל),"w"כתיבה שמוחקת,"a"הוספה לסוף.- תמיד
with: הקובץ נסגר אוטומטית, גם כשיש שגיאה. - קריאה:
read()(הכול),for line in file(שורה אחר שורה, מומלץ).strip()מסירה את\n. writeכותבת רקstr, ובלי ירידת שורה. מוסיפים"\n"בעצמכם.- נתיב עמיד:
Path(__file__).parent / "file.txt". בודקים קיום עם.exists()או תופסיםFileNotFoundError.
דוקומנטציה רשמית
- Reading and Writing Files — open ו-with
- Methods of File Objects — read, readline ו-write
- open() — mode ו-encoding