Dateien
Kurz gesagt: Eine Datei behält die Daten auf der Festplatte, auch nachdem das Programm endet; in Python öffnet open() die Datei zum Lesen oder Schreiben, und with schließt sie für dich.
Problem
Das Programm vergisst
Du schreibst ein Programm für deine Ausgaben. Du gibst sie abends ein und schließt es, und am nächsten Tag weiß es nichts mehr von gestern. Hier ist das kürzeste Beispiel:
visits = 0visits += 1print(f"Gestartet: {visits} Mal.")Gestartet: 1 Mal.Egal wie oft du es startest, es sagt „1 Mal“. Variablen leben im Speicher, solange das Programm läuft. Wenn es endet, wird der Speicher freigegeben, und alles verschwindet.
Damit es sich erinnert, muss das Programm die Daten dort ablegen, wo sie auch nach seinem Ende bleiben: in einer Datei auf der Festplatte.
Die Idee
Eine Datei mit open und with
Hier ist ein ganzes Programm, das eine Notiz in eine Datei schreibt und sie wieder liest:
try: with open("notes.txt", "w", encoding="utf-8") as file: file.write("Milch kaufen\nStromrechnung bis zum 15. bezahlen\n") with open("notes.txt", encoding="utf-8") as file: text = file.read() print(text, end="")except OSError as e: print(f"Problem mit der Datei: {e}")Milch kaufenStromrechnung bis zum 15. bezahlenopen(name, mode)öffnet die Datei."notes.txt"ist eine Datei mit diesem Namen in dem Ordner, aus dem das Programm gestartet wurde. Der Modus"w"(write) ist zum Schreiben: Er legt die Datei an, wenn es sie nicht gibt, und leert sie, wenn es sie gibt. Ohne Modus wird die Datei zum Lesen geöffnet.with ... as file:gibt dir die offene Datei unter dem Namenfileund schließt sie von selbst, wenn die eingerückten Zeilen zu Ende sind. Warum das Schließen wichtig ist, siehst du später.file.write(text)schreibt, undfile.read()liest die ganze Datei als einen String.encoding="utf-8"sagt, wie Buchstaben zu Bytes werden. Schreib es immer: Ohne es nimmt Python die Kodierung des Computers, und unter Windows ist das oft nicht UTF-8, sodass Umlaute wie „ä“, „ö“, „ü“ und das „€“ verstümmelt herauskommen.OSErrorist die allgemeine Exception für ein Problem mit einer Datei: Die Festplatte ist voll, die Datei ist gesperrt oder nicht da. Python zwingt dich nicht, sie zu fangen (der Compiler von Java verlangt es), aber ohneexcepthält das Programm an.
Starte das Programm und öffne dann den Ordner: notes.txt ist da, und du kannst sie mit jedem Texteditor öffnen. Der nächste Start findet sie.
Die Idee
Zeilen anhängen und lesen
Ausgaben kommen eine nach der anderen dazu. Der Modus "a" (append, „anhängen“) hängt am Ende an, statt die Datei neu zu schreiben. read().splitlines() teilt den gelesenen Text in Zeilen:
try: with open("expenses.txt", "w", encoding="utf-8") as file: file.write("Mittagessen;12.50\n") with open("expenses.txt", "a", encoding="utf-8") as file: file.write("Taxi;8.20\n") with open("expenses.txt", "a", encoding="utf-8") as file: file.write("Buch;18.90\n") with open("expenses.txt", encoding="utf-8") as file: lines = file.read().splitlines() total = 0 for line in lines: parts = line.split(";") total += float(parts[1]) print(f"{parts[0]}: {parts[1]} €") print(f"{len(lines)} Ausgaben, gesamt {total:.2f} €")except OSError as e: print(f"Problem mit der Datei: {e}")Mittagessen: 12.50 €Taxi: 8.20 €Buch: 18.90 €3 Ausgaben, gesamt 39.60 €lines ist eine Liste von Strings, einer für jede Zeile, ohne das Zeichen für den Zeilenumbruch. Du durchläufst sie mit for, und len(lines) ist die Anzahl der Zeilen. Mehr über Listen gibt es im nächsten Modul, „Collections“.
Genauer, für Neugierige
"a"legt die Datei an, wenn es sie nicht gibt, deshalb gibt das erste Anhängen keinen Fehler.read()liest die ganze Datei in den Speicher. Bei einer Datei von mehreren Gigabyte liest du besser Zeile für Zeile. Siehe die Karte überfor line in file.- In fremdem Code siehst du auch
file.readlines(). Es gibt ebenfalls eine Liste der Zeilen, aber jede behält ihr"\n"am Ende.
Probier es aus
Zwei Schichten
Was gibt das Programm aus?
try: with open("shift.txt", "w", encoding="utf-8") as file: file.write("Erste Schicht: Jonas\n") with open("shift.txt", "w", encoding="utf-8") as file: file.write("Zweite Schicht: Maria\n") with open("shift.txt", encoding="utf-8") as file: print(file.read(), end="")except OSError as e: print(e)"w" leert die Datei schon beim Öffnen und schreibt danach den neuen Inhalt. Die erste Zeile ist schon vor dem Lesen weg. Das ist der häufigste Grund für „meine Datei wurde gelöscht“.
Die Idee
Wenn die Datei nicht da ist
Die Einstellungen des Programms stehen in config.txt, und jemand hat sie gelöscht. Wenn niemand die Exception fängt:
def read_config(): with open("config.txt", encoding="utf-8") as file: return file.read()
print(read_config())Traceback (most recent call last): File "/app/app.py", line 6, in <module> print(read_config()) ^^^^^^^^^^^^^ File "/app/app.py", line 2, in read_config with open("config.txt", encoding="utf-8") as file: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^FileNotFoundError: [Errno 2] No such file or directory: 'config.txt'Python ordnet die Aufrufe umgekehrt wie Java: Ganz oben steht der äußerste, Zeile 6, wo read_config() aufgerufen wird. Darunter steht Zeile 2 in read_config, wo open die Datei nicht findet. Ganz unten steht der Fehler selbst: FileNotFoundError und die Meldung. Deshalb steht am Anfang „most recent call last“: Der letzte Aufruf ist ganz unten.
FileNotFoundError ist eine Unterklasse von OSError. Fang sie getrennt, vor dem allgemeinen Fall:
import os
def read_config(): with open("config.txt", encoding="utf-8") as file: return file.read()
try: print(read_config())except FileNotFoundError as e: print(f"Keine Datei {e.filename}. Ich nehme die Standardeinstellungen.")except OSError as e: print(f"Kann die Einstellungen nicht lesen: {e}")print(os.path.exists("config.txt"))Keine Datei config.txt. Ich nehme die Standardeinstellungen.Falsee.filename ist der Name der fehlenden Datei. os.path.exists(path) prüft, ob die Datei existiert. Die Prüfung ersetzt das except nicht: Die Datei kann zwischen der Prüfung und dem Lesen verschwinden, und es gibt noch andere Gründe, warum das Lesen scheitern kann.
Die Idee
Zeile für Zeile und with
Die Datei aus open kann in Teilen geschrieben und Zeile für Zeile gelesen werden, ohne dass sie ganz im Speicher ist. Sie hält die Datei offen, solange du mit ihr arbeitest, und muss deshalb am Ende geschlossen werden:
try: with open("report.txt", "w", encoding="utf-8") as out: for day in range(1, 4): out.write(f"Tag {day}: {day * 120} €\n")except OSError as e: print(f"Kann nicht schreiben: {e}")
try: with open("report.txt", encoding="utf-8") as file: for line in file: print(line.rstrip("\n"))except OSError as e: print(f"Kann nicht lesen: {e}")Tag 1: 120 €Tag 2: 240 €Tag 3: 360 €writeschreibt Text. Ein Zeilenumbruch kommt nicht von selbst: Das\nschreibst du.for line in filegibt die Zeilen eine nach der anderen, jede mit ihrem\nam Ende.rstrip("\n")entfernt es, sonst würdeprinteine Leerzeile dazwischen setzen.withschließt die Datei, wenn die eingerückten Zeilen zu Ende sind, egal auf welche Weise: auch bei einer Exception.
Das Schließen ist keine Formalität: Solange die Datei offen ist, kann der Text noch in ihrem Puffer im Speicher stehen und nicht auf der Festplatte. Damit der Code kurz bleibt, gibt es hier kein try:
import os
with open("report.txt", "w", encoding="utf-8") as out: for day in range(1, 4): out.write(f"Tag {day}: {day * 120} €\n") print(f"Im Block: {os.path.getsize('report.txt')} Bytes")print(f"Nach dem Block: {os.path.getsize('report.txt')} Bytes")with open("report.txt", encoding="utf-8") as file: for line in file: print(line.rstrip("\n"))Schritt für Schritt, in Worten
- os hat die Funktion für die Größe der Datei. Links ist der Speicher des Programms, rechts die Festplatte.
- open mit "w" legt die leere Datei report.txt an und ein Dateiobjekt mit einem Puffer im Speicher.
- write schreibt in den Puffer. Auf der Festplatte kommt noch nichts an.
- Die Datei auf der Festplatte hat noch 0 Bytes.
- Der with-Block endet und schließt die Datei. Der Puffer geht auf die Festplatte: Die Datei hat jetzt 45 Bytes.
- 45 Bytes für 39 Zeichen mit den Zeilenumbrüchen: In UTF-8 ist jeder lateinische Buchstabe 1 Byte, und € sind 3.
- for line in file liest die drei Zeilen von der Festplatte zurück.
Das haben wir in der Lektion über finally versprochen. Hier ist dasselbe Schreiben ohne with, mit finally:
out = open("report.txt", "w", encoding="utf-8")try: out.write("Tag 1: 120 €\n")finally: out.close()with erledigt genau das für dich: Es schließt die Datei, egal was im Block passiert.
Genauer, für Neugierige
- Ein
withkann mehrere Dateien enthalten, getrennt durch ein Komma:with open("app.log", encoding="utf-8") as src, open("errors.log", "w", encoding="utf-8") as out:. - Puffer. Die Datei sammelt den Text im Speicher und schickt ihn in großen Stücken auf die Festplatte. Jeder einzelne Schreibvorgang auf die Festplatte ist langsam, und so gibt es nur wenige davon.
file.flush()schickt den Puffer sofort, ohne die Datei zu schließen. - Bytes und Zeichen. Darunter ist eine Datei eine Folge von Bytes. Die Modi
"rb"und"wb"lesen und schreiben Bytes: für Fotos, Archive, alles, was kein Text ist. Ohnebist die Datei eine Textdatei und verwandelt Bytes nach demencodingin Zeichen. file.readline()liest eine Zeile. Wenn die Datei zu Ende ist, gibt es einen leeren String""zurück. Eine leere Zeile in der Datei ist"\n", deshalb verwechselt man die beiden nicht.
Probier es aus
Wann wird geschlossen?
Jede Klasse mit den Methoden __enter__ und __exit__ kann in einem with stehen. __enter__ wird am Anfang des Blocks aufgerufen und __exit__ an seinem Ende, auch bei einer Exception. Hier ist der Kassendrucker aus der Lektion über finally, als Ressource:
Was gibt das Programm aus?
class Printer: def __enter__(self): print("Der Drucker ist belegt.") return self
def __exit__(self, exc_type, exc, tb): print("Der Drucker ist frei.")
try: with Printer(): print("Drucke den Kassenbon...") qty = int("drei") print(f"Menge: {qty}")except ValueError: print("Falsche Menge.")int("drei") wirft ValueError, und die Zeile „Menge“ wird übersprungen. Beim Verlassen von with ruft Python sofort __exit__ auf, noch vor except. Deshalb kommt „frei“ vor „Falsche Menge.“ Wenn except läuft, ist der Drucker schon freigegeben.Probier es aus
Finde den Fehler
0 aus. In welcher Zeile ist der Fehler?Wähle die Zeile mit dem Fehler.
with geöffnet und nicht geschlossen. Der Text steht noch im Puffer im Speicher und hat die Festplatte nicht erreicht, deshalb ist die Datei beim Lesen leer. Das Programm meldet keinen Fehler. Es zählt still falsch. Öffne sie mit with open(...) as out: und lies die Datei nach dem Block: Dann ist sie geschlossen, und die Zeile ist da.Die Idee
Ordner
Die Sicherungskopien der Kasse kommen in einen Ordner, eine pro Tag:
import os
backup = "backup"try: os.makedirs(os.path.join(backup, "old"), exist_ok=True) with open(os.path.join(backup, "2026-10-01.csv"), "w", encoding="utf-8") as file: file.write("Brötchen,0.60\n") with open(os.path.join(backup, "2026-10-02.csv"), "w", encoding="utf-8") as file: file.write("Brötchen,0.60\nTee,1.80\n") for name in sorted(os.listdir(backup)): p = os.path.join(backup, name) if os.path.isdir(p): print(f"{name}/") else: print(f"{name} ({os.path.getsize(p)} Bytes)") os.remove(os.path.join(backup, "2026-10-01.csv")) print(os.path.exists(os.path.join(backup, "2026-10-01.csv")))except OSError as e: print(e)2026-10-01.csv (15 Bytes)2026-10-02.csv (24 Bytes)old/False| Funktion | Was sie tut |
|---|---|
os.makedirs(p, exist_ok=True) |
legt den Ordner und alle Ordner darüber an, wenn es sie nicht gibt |
os.path.join(dir, "name") |
der Pfad zu name in dir |
os.path.isdir(p), os.path.exists(p) |
ist es ein Ordner, ist es da |
os.path.getsize(p) |
die Größe in Bytes |
os.remove(p) |
löscht eine Datei (os.rmdir(p) löscht einen leeren Ordner) |
os.listdir(dir) |
eine Liste mit den Namen von allem im Ordner |
Die Größen sind 15 und 24 Bytes, obwohl es weniger Zeichen sind: In UTF-8 ist das „ö“ in „Brötchen“ 2 Bytes.
os.listdir gibt die Namen in keiner bestimmten Reihenfolge, deshalb sortieren wir sie mit sorted.Genauer, für Neugierige
os.rmdirlöscht nur einen leeren Ordner. Sonst meldet esOSError: [Errno 39] Directory not empty.shutil.rmtree(p)(das Modulshutil) löscht den Ordner mit allem darin, also sei vorsichtig damit.shutil.copy(src, dst)kopiert eine Datei, undos.rename(src, dst)verschiebt oder benennt sie um.- In viel Code siehst du das Modul
pathlib:Path("backup") / "old"ist der Pfad zum Ordneroldinbackup, undp.exists()undp.read_text(encoding="utf-8")sind Methoden des Pfads. Es ist dasselbe, mit Objekten statt Funktionen. - Der Pfad
"notes.txt"ist relativ: zu dem Ordner, aus dem das Programm gestartet wurde."/home/maria/notes.txt"ist absolut.os.path.abspath("notes.txt")zeigt genau, wo die Datei ist.
Übung
Programme, die sich erinnern
Löse die Aufgaben in deinem Editor. In jeder Aufgabe legt das Programm die Eingabedatei zuerst selbst an, mit open(..., "w"), damit du es sofort ausprobieren kannst.
1. Eine Konfigurationsdatei. Lege server.conf mit diesem Inhalt an, lies sie dann und gib jede Einstellung als Schlüssel: Wert aus. Überspringe die leeren Zeilen und die Zeilen, die mit # beginnen. Tipp: split("=", 1) teilt nur am ersten = (die Zahl sagt, wie oft geschnitten wird).
# Servereinstellungenmax_connections=500log_file_path=/var/log/server.log
db_timeout=30max_connections: 500log_file_path: /var/log/server.logdb_timeout: 302. Ein Filter für eine Logdatei. Lege app.log mit vier Zeilen an (zwei INFO und zwei ERROR). Lies ein Level von der Tastatur, zum Beispiel ERROR, und schreib die Zeilen mit diesem Level in errors.log Zeile für Zeile, mit beiden Dateien in einem with. Gib errors.log aus und wie viele Zeilen du geschrieben hast.
ERRORERROR: Failed to load configurationERROR: Null pointer exception2 Zeilen in errors.log geschrieben3. Der Inhalt eines Ordners. Lege einen Ordner project mit den Dateien document.txt, image.jpg, script.js und dem Ordner backup an. Schreib ihre Namen, alphabetisch sortiert, in directory_contents.txt, mit einem / nach den Namen von Ordnern. Gib die Datei aus.
backup/document.txtimage.jpgscript.js4. Eine Indexdatei. Lege text.txt mit den drei Zeilen unten an. Lies ein Wort von der Tastatur und schreib in index.txt jede Stelle, an der es vorkommt, in der Form Zeile:Position: die Nummer der Zeile (ab 1) und den Index in der Zeile (ab 0). Wenn das Wort zweimal in einer Zeile steht, sind das zwei Einträge.
hello worldworld hellohello again, hellohello1:02:63:03:13Weitere Aufgaben
- Zähler für die Starts. Repariere das Programm aus der ersten Karte: Zähl die Starts in
visits.txt. Wenn es die Datei nicht gibt, fang bei 0 an. - Suchen und Ersetzen in einer Datei. Die Aufgabe aus der Lektion „Reguläre Ausdrücke: Gruppen und Ersetzen“: Lies eine Datei, ersetze „alt“ durch „neu“ und schreib das Ergebnis in eine neue Datei.
- Datumsangaben aus einer Datei. Die Datumsaufgabe aus der Lektion „Reguläre Ausdrücke“: Lies die Datumsangaben aus einer Datei, eine pro Zeile.
- Eine Kopie mit Zeilennummern. Lies eine Datei und schreib eine Kopie, in der jede Zeile mit ihrer Nummer beginnt:
1: ...,2: ....
Test
Teste dich
Verwandte Lektionen
Abschnitt „Verwandte Lektionen“- Baut auf: Interfaces als abstrakte Klassen · Exceptions · try, except und finally · Strings
- Geht weiter in: Daten in Dateien · Hashcodes und Hashfunktionen · Netzwerkprogrammierung · Datenbanken
- Aufgaben zum Thema: Text und Dateien