Zum Inhalt
wannacode

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 = 0
visits += 1
print(f"Gestartet: {visits} Mal.")
Ausgabe
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:

notes.py
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}")
Ausgabe
Milch kaufen
Stromrechnung bis zum 15. bezahlen
  • open(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 Namen file und 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, und file.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.
  • OSError ist 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 ohne except hä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}")
Ausgabe
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 über for 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)
Antwort

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:

app.py
def read_config():
with open("config.txt", encoding="utf-8") as file:
return file.read()
print(read_config())
Fehler
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"))
Ausgabe
Keine Datei config.txt. Ich nehme die Standardeinstellungen.
False

e.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}")
Ausgabe
Tag 1: 120 €
Tag 2: 240 €
Tag 3: 360 €
  • write schreibt Text. Ein Zeilenumbruch kommt nicht von selbst: Das \n schreibst du.
  • for line in file gibt die Zeilen eine nach der anderen, jede mit ihrem \n am Ende. rstrip("\n") entfernt es, sonst würde print eine Leerzeile dazwischen setzen.
  • with schließ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
  1. os hat die Funktion für die Größe der Datei. Links ist der Speicher des Programms, rechts die Festplatte.
  2. open mit "w" legt die leere Datei report.txt an und ein Dateiobjekt mit einem Puffer im Speicher.
  3. write schreibt in den Puffer. Auf der Festplatte kommt noch nichts an.
  4. Die Datei auf der Festplatte hat noch 0 Bytes.
  5. Der with-Block endet und schließt die Datei. Der Puffer geht auf die Festplatte: Die Datei hat jetzt 45 Bytes.
  6. 45 Bytes für 39 Zeichen mit den Zeilenumbrüchen: In UTF-8 ist jeder lateinische Buchstabe 1 Byte, und € sind 3.
  7. 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 with kann 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. Ohne b ist die Datei eine Textdatei und verwandelt Bytes nach dem encoding in 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.")
Antwort

Probier es aus

Finde den Fehler

Das Programm schreibt eine Bestellung und zählt die Zeilen in der Datei. Es gibt 0 aus. In welcher Zeile ist der Fehler?

Wähle die Zeile mit dem Fehler.

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)
Ausgabe
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.rmdir löscht nur einen leeren Ordner. Sonst meldet es OSError: [Errno 39] Directory not empty. shutil.rmtree(p) (das Modul shutil) löscht den Ordner mit allem darin, also sei vorsichtig damit.
  • shutil.copy(src, dst) kopiert eine Datei, und os.rename(src, dst) verschiebt oder benennt sie um.
  • In viel Code siehst du das Modul pathlib: Path("backup") / "old" ist der Pfad zum Ordner old in backup, und p.exists() und p.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).

server.conf
# Servereinstellungen
max_connections=500
log_file_path=/var/log/server.log
db_timeout=30
Ausgabe
max_connections: 500
log_file_path: /var/log/server.log
db_timeout: 30

2. 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.

Eingabe
ERROR
Ausgabe
ERROR: Failed to load configuration
ERROR: Null pointer exception
2 Zeilen in errors.log geschrieben

3. 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.

directory_contents.txt
backup/
document.txt
image.jpg
script.js

4. 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.

text.txt
hello world
world hello
hello again, hello
Eingabe
hello
index.txt
1:0
2:6
3:0
3:13

Weitere 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

1. open("config.txt", encoding="utf-8") findet die Datei nicht, und es gibt kein try darum. Was passiert?
2. Du öffnest eine Datei mit "w" und schreibst "A\n". Dann öffnest du sie wieder mit "w" und schreibst "B\n". Was steht in der Datei?
3. Wann schließt with die Datei?
4. Was gibt file.readline() zurück, wenn die Datei zu Ende ist?
5. Du öffnest eine Datei mit open() ohne with und schließt sie nicht. Was kann in der Datei stehen, während das Programm noch läuft?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·