4. Unterrichtsblock

Dateipfade

Programme speichern Notenlisten, Einstellungen oder Logdateien. Damit Python eine Datei findet, braucht es ihren Pfad. Für Pfade gibt es das Modul pathlib: Ein Path ist ein Pfad, mit dem man rechnen kann.

Ausdruckliefert
Path.cwd()Arbeitsverzeichnis: der Ordner, aus dem python gestartet wurde
Path(__file__).resolve().parentSkript-Ordner: der Ordner, in dem die .py-Datei liegt
ordner / "datei.txt"neuer Pfad (Ordner und Name zusammengesetzt)
pfad.name / pfad.stem / pfad.suffixDateiname / Name ohne Endung / Endung
pfad.parentübergeordneter Ordner
pfad.exists()True, wenn es die Datei oder den Ordner gibt

Arbeitsverzeichnis und Skript-Ordner

Python kennt zwei wichtige Ordner. Das Arbeitsverzeichnis ist der Ordner, in dem das Terminal gerade steht, als du python gestartet hast. Der Skript-Ordner ist der Ordner, in dem deine .py-Datei liegt. Beide sind nur dann gleich, wenn du das Skript aus seinem eigenen Ordner startest. Die Ausgabe hängt von deinem Rechner ab:

from pathlib import Path

print(Path.cwd())                         # Arbeitsverzeichnis
ORDNER = Path(__file__).resolve().parent  # Skript-Ordner
print(ORDNER)

Pfade zusammensetzen

Mit / hängst du Ordner und Dateinamen an. Python setzt automatisch das richtige Trennzeichen: unter Windows \, unter macOS und Linux /. Das Beispiel liegt hier in C:\schule\python:

from pathlib import Path

ORDNER = Path(__file__).resolve().parent
pfad = ORDNER / "daten" / "kunden.csv"
print(pfad)
print(pfad.name)
print(pfad.stem)
print(pfad.suffix)
print(pfad.parent)
print(pfad.exists())
Ausgabe im Terminal (Windows)
C:\schule\python\daten\kunden.csv
kunden.csv
kunden
.csv
C:\schule\python\daten
False

Eine Ebene höher: ..

.. bedeutet „eine Ebene höher“. resolve() rechnet einen Pfad mit .. aus. Dasselbe Ziel erreichst du mit .parent:

from pathlib import Path

ORDNER = Path(__file__).resolve().parent
print(ORDNER / ".." / "shared.txt")
print((ORDNER / ".." / "shared.txt").resolve())
print(ORDNER.parent / "shared.txt")
Ausgabe im Terminal (Windows)
C:\schule\python\..\shared.txt
C:\schule\shared.txt
C:\schule\shared.txt
Merke: Baue Pfade immer vom Skript-Ordner aus: ORDNER / "daten" / "noten.txt". Ein Pfad wie "daten/noten.txt" hängt vom Arbeitsverzeichnis ab und findet die Datei nicht mehr, wenn das Programm aus einem anderen Ordner gestartet wird.

Aufgabe 1 – Wo bin ich?

Zeit: ca. 8 Minuten

Finde heraus, was Path.cwd() und der Skript-Ordner ORDNER bei dir anzeigen. Lege dafür eine neue Datei block_04_pfade_dateien.py an, die so beginnt:

from pathlib import Path

ORDNER = Path(__file__).resolve().parent
  1. Gib Path.cwd() und ORDNER aus.
  2. Starte das Skript zweimal im Terminal: einmal aus dem Ordner des Skripts (python block_04_pfade_dateien.py) und einmal aus dem Ordner darüber (python uebungen/block_04_pfade_dateien.py). Sage vorher voraus: Welcher der beiden Pfade ändert sich, welcher nicht?
  3. Baue mit / den Pfad zu einer Datei daten/kunden.csv im Skript-Ordner und gib ihn aus. Gib außerdem pfad.name, pfad.suffix und pfad.parent aus. Die Datei muss dafür nicht existieren.

Dateien öffnen und lesen

Die Funktion open(pfad, modus, encoding="utf-8") öffnet eine Datei und liefert ein Dateiobjekt, mit dem du lesen oder schreiben kannst. Zusammen mit with wird die Datei am Ende des eingerückten Blocks automatisch wieder geschlossen (mehr dazu im nächsten Abschnitt). Der Modus legt fest, was du mit der Datei vorhast:

ModusBedeutungWenn die Datei fehltWenn die Datei schon da ist
"r"lesen (Standard)FileNotFoundErrorwird gelesen
"w"schreibenwird angelegtInhalt wird gelöscht und neu geschrieben
"a"anhängenwird angelegtNeues kommt ans Ende
"x"nur neu anlegenwird angelegtFileExistsError

Mit einem zusätzlichen b (zum Beispiel "rb") liest du Binärdateien wie Bilder. In diesem Block arbeiten wir nur mit Textdateien.

Merke: Gib bei Textdateien immer encoding="utf-8" an. Ohne diese Angabe benutzt Windows eine andere Codierung, und aus „Ä“ wird Zeichensalat oder ein UnicodeDecodeError.

Die ganze Datei lesen: read()

Für die Beispiele legen wir zuerst eine kleine Datei an (Schreiben kommt gleich noch genauer) und lesen sie dann komplett mit read():

from pathlib import Path

ORDNER = Path(__file__).resolve().parent
pfad = ORDNER / "spruch.txt"

with open(pfad, "w", encoding="utf-8") as datei:
    datei.write("Wer A sagt,\nmuss nicht B sagen.\nEr kann auch erkennen,\ndass A falsch war.\n")

with open(pfad, "r", encoding="utf-8") as datei:
    inhalt = datei.read()
print(inhalt)
print(len(inhalt), "Zeichen")
Ausgabe im Terminal
Wer A sagt,
muss nicht B sagen.
Er kann auch erkennen,
dass A falsch war.

74 Zeichen

read(10) liest nur die ersten 10 Zeichen.

Zeile für Zeile: readline() und for

readline() liest genau eine Zeile, beim nächsten Aufruf die nächste. Mit for zeile in datei gehst du alle Zeilen der Reihe nach durch:

with open(pfad, "r", encoding="utf-8") as datei:
    erste = datei.readline()
    zweite = datei.readline()
print(erste.strip())
print(zweite.strip())

with open(pfad, "r", encoding="utf-8") as datei:
    nummer = 1
    for zeile in datei:
        print(nummer, zeile.strip())
        nummer += 1
Ausgabe im Terminal
Wer A sagt,
muss nicht B sagen.
1 Wer A sagt,
2 muss nicht B sagen.
3 Er kann auch erkennen,
4 dass A falsch war.
Merke: read() holt die ganze Datei auf einmal in den Speicher. Große Dateien, zum Beispiel Logdateien mit Millionen Zeilen, liest du besser Zeile für Zeile mit for.

Aufgabe 2 – Einkaufszettel

Zeit: ca. 10 Minuten

Ein Einkaufszettel soll als Textdatei gespeichert und wieder eingelesen werden.

  1. Schreibe mit with open(..., "w", encoding="utf-8") die Datei einkauf.txt mit drei Zeilen: Brot, Milch, Äpfel.
  2. Lies die Datei mit read() und gib den Inhalt aus.
  3. Lies sie erneut und gib sie mit for zeile in datei: print(zeile) aus. Sage vorher voraus: Warum steht jetzt zwischen den Zeilen eine Leerzeile? Behebe das.
  4. Lass einmal encoding="utf-8" beim Schreiben weg, aber nicht beim Lesen. Was passiert mit „Äpfel“? Baue es danach wieder ein.

Dateien schließen und with

Eine geöffnete Datei belegt Ressourcen. Unter Windows ist sie oft für andere Programme gesperrt, und Geschriebenes landet erst beim Schließen sicher auf der Festplatte. Ohne with musst du die Datei selbst mit close() schließen:

datei = open(pfad, "r", encoding="utf-8")
print(datei.readline().strip())
datei.close()
print(datei.closed)
Ausgabe im Terminal
Wer A sagt,
True

Tritt zwischen open() und close() ein Fehler auf, wird close() nie erreicht. with schließt die Datei dagegen immer, sobald der eingerückte Block endet, auch bei einem Fehler:

with open(pfad, "r", encoding="utf-8") as datei:
    print(datei.closed)
print(datei.closed)
Ausgabe im Terminal
False
True
Merke: Verwende immer with. In älteren Beispielen, auch im Internet, steht oft open() ohne with und ohne encoding. Das funktioniert, ist aber fehleranfälliger.

Dateien schreiben und erstellen

write() schreibt Text in die Datei. Einen Zeilenumbruch fügt es nicht automatisch an, dafür schreibst du selbst \n. Zahlen wandelst du vorher in Text um, am einfachsten mit einem f-String. Der Modus entscheidet, ob alter Inhalt erhalten bleibt:

log = ORDNER / "log.txt"

with open(log, "w", encoding="utf-8") as datei:
    datei.write("Start\n")

with open(log, "w", encoding="utf-8") as datei:
    datei.write("Neustart\n")

with open(log, "a", encoding="utf-8") as datei:
    datei.write(f"Punkte: {3 * 7}\n")

with open(log, "r", encoding="utf-8") as datei:
    print(datei.read())
Ausgabe im Terminal
Neustart
Punkte: 21

„Start“ fehlt: Das zweite "w" hat die Datei geleert und neu geschrieben, erst "a" hat etwas angehängt.

Nur neu anlegen: „x“

Mit "x" legt Python eine Datei nur an, wenn es sie noch nicht gibt. Sonst kommt ein FileExistsError, und nichts wird überschrieben:

try:
    with open(log, "x", encoding="utf-8") as datei:
        datei.write("Neu\n")
except FileExistsError:
    print("log.txt gibt es schon, nichts überschrieben.")
Ausgabe im Terminal
log.txt gibt es schon, nichts überschrieben.
Merke: "w" löscht den alten Inhalt ohne Rückfrage. Willst du etwas ergänzen, nimm "a". Darf nie etwas überschrieben werden, nimm "x".

Ordner anlegen, Dateien finden und löschen

Auch Ordner verwaltest du mit pathlib. In vielen älteren Beispielen stehen dafür noch Befehle aus dem Modul os, sie machen dasselbe:

pathlibälter mit osWirkung
pfad.exists()os.path.exists(pfad)gibt es die Datei / den Ordner?
ordner.mkdir(exist_ok=True)os.mkdir(ordner)Ordner anlegen
ordner.glob("*.txt")os.listdir(ordner)Inhalt eines Ordners durchgehen
pfad.unlink()os.remove(pfad)Datei löschen
ordner.rmdir()os.rmdir(ordner)leeren Ordner löschen

Ordner anlegen und Inhalt auflisten

mkdir(exist_ok=True) legt einen Ordner an und meckert nicht, wenn es ihn schon gibt. Mit parents=True werden auch fehlende Ordner darüber angelegt. glob("*.txt") findet alle Dateien mit dieser Endung, stat().st_size liefert die Größe in Bytes:

daten = ORDNER / "daten"
daten.mkdir(exist_ok=True)

for name in ["a.txt", "b.txt", "notiz.md"]:
    with open(daten / name, "w", encoding="utf-8") as datei:
        datei.write("Hallo\n")

for datei in sorted(daten.glob("*.txt")):
    print(datei.name, datei.stat().st_size, "Bytes")
print(daten.is_dir(), (daten / "notiz.md").is_file())
Ausgabe im Terminal
a.txt 7 Bytes
b.txt 7 Bytes
True True

„Hallo“ und der Zeilenumbruch sind 6 Zeichen. Unter Windows wird \n in der Datei als zwei Zeichen gespeichert, deshalb 7 Bytes.

Dateien und Ordner löschen

unlink() löscht eine Datei, rmdir() einen Ordner, aber nur, wenn er leer ist. Einen vollen Ordner leerst du also zuerst:

ziel = daten / "notiz.md"
if ziel.exists():
    ziel.unlink()
    print("notiz.md gelöscht")

try:
    daten.rmdir()
except OSError:
    print("Der Ordner ist nicht leer.")

for datei in daten.glob("*"):
    datei.unlink()
daten.rmdir()
print("daten gibt es noch:", daten.exists())
Ausgabe im Terminal
notiz.md gelöscht
Der Ordner ist nicht leer.
daten gibt es noch: False
Merke: Gelöschte Dateien landen nicht im Papierkorb, sie sind sofort weg. Prüfe vorher mit exists() und frage bei wichtigen Dateien nach.

Übungsaufgaben

Die Aufgaben 3 bis 10 sind die Kernaufgaben, wer schneller ist, macht mit den Bonusaufgaben weiter. Schreibe alle Aufgaben in deine Datei block_04_pfade_dateien.py aus Aufgabe 1. Sie beginnt immer mit diesem Kopf:

from pathlib import Path

ORDNER = Path(__file__).resolve().parent

Alle Dateien, die du erzeugst, landen im Ordner deines Skripts. Schau nach jedem Lauf im Explorer von VS Code nach, was entstanden ist.

Aufgabe 3 – Überschreiben oder anhängen?

Zeit: ca. 8 Minuten

Ein Programm soll mitschreiben, wann es gestartet wurde.

from datetime import datetime

jetzt = datetime.now().strftime("%d.%m.%Y %H:%M:%S")
  1. Schreibe die Zeile f"Gestartet: {jetzt}" mit Modus "w" in starts.txt. Starte das Programm dreimal. Wie viele Zeilen stehen in der Datei?
  2. Ändere den Modus auf "a" und starte wieder dreimal. Und jetzt?
  3. Was passiert mit Modus "x" beim zweiten Start? Teste es mit einer neuen Datei einmalig.txt und fange den Fehler ab.

Aufgabe 4 – Die Datei fehlt

Zeit: ca. 8 Minuten

Was passiert, wenn eine Datei nicht da ist? Hier wiederholst du try/except aus Block 3.

  1. Öffne die Datei gibtsnicht.txt zum Lesen. Welchen Fehler meldet Python?
  2. Fange den Fehler mit try/except ab und gib eine verständliche Meldung mit dem Dateinamen aus.
  3. Löse dasselbe noch einmal ohne try, indem du vorher mit pfad.exists() prüfst. Welche Lösung findest du besser? Begründe kurz in einem Kommentar.

Aufgabe 5 – Notenliste auswerten

Zeit: ca. 12 Minuten

Eine Lehrkraft hat die Noten einer Probe als Textdatei: pro Zeile Name und Note, getrennt durch ein Semikolon.

noten_text = """Lina;2
Tom;3
Ayşe;1
Ben;4
Mia;2
"""
  1. Schreibe noten_text in die Datei noten.txt.
  2. Lies die Datei Zeile für Zeile, trenne Name und Note mit split(";") und gib eine Tabelle aus (Name linksbündig, Note rechtsbündig).
  3. Berechne den Durchschnitt und gib ihn mit zwei Nachkommastellen aus.
  4. Gib aus, wer die beste Note hat.

Zusatz: Füge in noten.txt von Hand eine kaputte Zeile ein (z. B. Max; oder eine Leerzeile). Dein Programm soll sie überspringen und melden, statt abzustürzen.

Aufgabe 6 – Das Gästebuch

Zeit: ca. 10 Minuten

Besucher sollen Einträge hinterlassen können, die auch nach dem Programmende erhalten bleiben.

  1. Frage in einer Schleife nach Einträgen. Eine leere Eingabe beendet die Eingabe.
  2. Hänge jeden Eintrag mit Datum an die Datei gaestebuch.txt an, z. B. 07.10.2026 | Schöne Seite!
  3. Gib danach das komplette Gästebuch nummeriert aus: 01: 07.10.2026 | Schöne Seite!
  4. Starte das Programm ein zweites Mal. Bleiben die alten Einträge erhalten? Warum?

Aufgabe 7 – Was geht am Wochenende?

Zeit: ca. 10 Minuten

Eine kleine Suchfunktion für Textdateien, getestet am Anfang von „Max und Moritz“.

gedicht = """Ach was muß man oft von bösen
Kindern hören oder lesen!
Wie zum Beispiel hier von diesen,
Welche Max und Moritz hießen.
Die, anstatt durch weise Lehren
Sich zum Guten zu bekehren,
Oftmals noch darüber lachten
Und sich heimlich lustig machten.
Ja, zur Übeltätigkeit,
Ja, dazu ist man bereit!
Menschen necken, Tiere quälen,
Äpfel, Birnen, Zwetschen stehlen
Das ist freilich angenehmer
Und dazu auch viel bequemer,
Als in Kirche oder Schule
Festzusitzen auf dem Stuhle.
"""
  1. Speichere das Gedicht in maxundmoritz.txt.
  2. Frage nach einem Suchwort und gib alle Zeilen aus, in denen es vorkommt, jeweils mit Zeilennummer: Zeile 11: Menschen necken, Tiere quälen,
  3. Groß- und Kleinschreibung soll egal sein („max“ findet „Max“).
  4. Wird nichts gefunden, erscheint eine passende Meldung. Sonst steht am Ende z. B. „3 Treffer“.

Aufgabe 8 – Ordnung im Projekt

Zeit: ca. 8 Minuten

Bisher liegen alle Dateien neben dem Skript. Das wird schnell unübersichtlich.

  1. Lege mit mkdir(exist_ok=True) einen Unterordner daten an. Was bedeutet exist_ok=True? Was passiert ohne?
  2. Schreibe die Datei aus Aufgabe 5 künftig nach daten/noten.txt.
  3. Gib alle .txt-Dateien im Skript-Ordner mit Name und Größe in Bytes aus. Tipp: ORDNER.glob("*.txt") und pfad.stat().st_size
  4. Denkfrage: Warum ist open("daten/noten.txt") gefährlicher als open(ORDNER / "daten" / "noten.txt")? Denke an Aufgabe 1.

Aufgabe 9 – Nachbarordner und eine Ebene höher

Zeit: ca. 10 Minuten

In echten Projekten liegen Programm, Daten und Logs in getrennten Ordnern: projekt/shared.txt, projekt/daten/kunden.txt, projekt/logs/ und projekt/src/ für das Programm. Diese Struktur baust du im Skript-Ordner nach.

projekt = ORDNER / "projekt"
src = projekt / "src"       # hier läge in echt das Programm main.py
  1. Lege die Ordner projekt/daten, projekt/logs und projekt/src mit mkdir(parents=True, exist_ok=True) an. Schreibe drei Kunden in projekt/daten/kunden.txt und zwei Zeilen in projekt/shared.txt.
  2. Stell dir vor, dein Programm liegt in src. Baue von src aus mit ".." den Pfad zu daten/kunden.txt (ein Nachbarordner) und gib die erste Zeile aus (readline()).
  3. Zähle von src aus, wie viele Zeilen shared.txt eine Ebene höher hat. Kontrolle: 2.
  4. Gib den Pfad aus Schritt 2 einmal ohne und einmal mit .resolve() aus. Was ist der Unterschied?

Zusatz: Was bewirkt parents=True? Was passiert ohne, wenn projekt noch nicht existiert?

Aufgabe 10 – Aufräumen mit Rückfrage

Zeit: ca. 8 Minuten

Ein Programm soll einen Ordner samt Inhalt löschen, aber nur nach Rückfrage. Die Vorgabe legt dir dafür einen Testordner mit drei Dateien an.

test = ORDNER / "testordner"
test.mkdir(exist_ok=True)
for nummer in range(1, 4):
    with open(test / f"notiz_{nummer}.txt", "w", encoding="utf-8") as datei:
        datei.write(f"Notiz {nummer}\n")
  1. Führe die Vorgabe aus und prüfe im Explorer, dass testordner mit drei Dateien entstanden ist.
  2. Frage nach: „Soll der Ordner testordner gelöscht werden? (ja/nein)“. Nur bei „ja“ (egal ob groß oder klein geschrieben) wird gelöscht.
  3. Lösche zuerst alle Dateien im Ordner (glob("*") und unlink()) und danach den leeren Ordner (rmdir()). Gib aus, wie viele Dateien gelöscht wurden.
  4. Was passiert bei rmdir(), solange noch Dateien im Ordner sind? Und wenn der Ordner gar nicht existiert? Sorge dafür, dass dein Programm in beiden Fällen nicht abstürzt.

Bonusaufgabe 1 – Das Backup

Wichtige Daten sichert man regelmäßig. Ein Backup-Programm kopiert alle Textdateien aus daten in einen Ordner mit dem heutigen Datum.

  1. Lege den Ordner daten/backup_JJJJ-MM-TT mit dem heutigen Datum an (Tipp: datetime.now().strftime("%Y-%m-%d")).
  2. Kopiere jede .txt-Datei aus daten in diesen Ordner, indem du sie liest und unter gleichem Namen neu schreibst.
  3. Gib am Ende aus, wie viele Dateien und wie viele Bytes gesichert wurden.

Zusatz: Ein zweiter Lauf am selben Tag würde das Backup überschreiben. Wie verhinderst du das? Baue eine Lösung ein.

Bonusaufgabe 2 – Die Textstatistik

Werte das Gedicht aus Aufgabe 7 aus und speichere einen kleinen Bericht.

  1. Lies daten/maxundmoritz.txt und zähle Zeilen, Wörter (split()) und Zeichen ohne Zeilenumbrüche.
  2. Finde die längste Zeile und gib sie mit ihrer Länge aus.
  3. Schreibe die Ergebnisse als Bericht in daten/statistik.txt und gib die Datei danach zur Kontrolle aus. Kontrolle: 16 Zeilen.
Kurz zusammengefasst:
  • Pfade immer vom Skript-Ordner aus bauen: ORDNER = Path(__file__).resolve().parent, dann ORDNER / "daten" / "datei.txt". .. geht eine Ebene höher.
  • Dateien immer mit with open(pfad, modus, encoding="utf-8") as datei: öffnen, dann werden sie automatisch geschlossen.
  • "r" lesen, "w" überschreiben, "a" anhängen, "x" nur neu anlegen.
  • Lesen mit read(), readline() oder for zeile in datei (Zeilenende mit strip() entfernen), schreiben mit write() und eigenem \n.
  • Ordner: mkdir(exist_ok=True), glob(), exists(). Löschen mit unlink() und rmdir() (nur leere Ordner), ohne Papierkorb.