9. Dateien und Datenformate
Dateien und Datenformate
Abschnitt betitelt „Dateien und Datenformate“Alle bisherigen Programme hatten ein Gedächtnisproblem: Mit dem Programmende waren sämtliche Daten verloren. Reale Anwendungen müssen Daten dauerhaft speichern (Persistenz) und, mindestens ebenso wichtig, mit anderen Programmen austauschen: Eine Schnittsoftware exportiert eine Liste von Markern, ein Python-Skript wertet sie aus; ein eigenes Programm erzeugt eine Tabelle, die in einer Tabellenkalkulation weiterverarbeitet wird.
Dieses Kapitel behandelt beides: den grundlegenden Umgang mit Dateien in Python und die zwei wichtigsten textbasierten Datenformate, CSV für tabellarische und JSON für strukturierte Daten. Der Lehrplan spricht hier von Daten-Schnittstellen, und dieser Begriff trifft den Kern: Das Dateiformat ist der Vertrag zwischen zwei Programmen, die einander sonst nicht kennen. Hält sich der Erzeuger an das Format, kann jeder Verarbeiter die Daten lesen, unabhängig von Programmiersprache und Betriebssystem.
Dateien lesen und schreiben
Abschnitt betitelt „Dateien lesen und schreiben“Der Zugriff auf Dateien läuft über die eingebaute Funktion open(), und zwar grundsätzlich in der with-Form:
with open("notes.txt", "w", encoding="utf-8") as file: file.write("Color grading for scene 4 still missing.\n") file.write("Check audio sync in the interview part.\n")
with open("notes.txt", encoding="utf-8") as file: content = file.read()
print(content)Die Bestandteile:
- Der zweite Parameter ist der Modus:
"r"lesen (Standard),"w"schreiben (Achtung: überschreibt eine bestehende Datei ersatzlos),"a"anhängen. withmacht die Datei zu einem Kontextmanager: Beim Verlassen des Blocks wird sie garantiert geschlossen, auch wenn mittendrin eine Exception auftritt. Offene Dateien sind eine klassische Quelle schleichender Fehler (gesperrte Dateien, nicht geschriebene Pufferinhalte); diewith-Form schließt diese Fehlerquelle vollständig.open()ohnewithhat in neuem Code nichts verloren.encoding="utf-8"legt die Zeichenkodierung fest, also die Übersetzung zwischen Text und Bytes.
Große Dateien liest man nicht mit read() am Stück, sondern zeilenweise; die Datei selbst ist iterierbar:
with open("render.log", encoding="utf-8") as file: for line in file: if "ERROR" in line: print(line.strip())strip() entfernt den Zeilenumbruch am Ende jeder gelesenen Zeile, ein Detail, das andernfalls regelmäßig doppelte Leerzeilen in der Ausgabe erzeugt.
Fehlt die Datei, löst open() einen FileNotFoundError aus; bei fehlenden Rechten kommt ein PermissionError. Beide sind Subklassen von OSError und werden mit den Mitteln aus Kapitel 7 behandelt, und zwar dort, wo eine sinnvolle Reaktion möglich ist:
try: with open("notes.txt", encoding="utf-8") as file: content = file.read()except FileNotFoundError: content = "" # a missing notes file simply means: no notes yetCSV: tabellarische Daten
Abschnitt betitelt „CSV: tabellarische Daten“CSV (Comma-Separated Values) ist das einfachste verbreitete Datenformat: eine Textdatei, eine Zeile pro Datensatz, Felder durch Kommas getrennt, üblicherweise mit einer Kopfzeile:
filename,duration_seconds,fpsintro.mp4,90,25interview.mp4,845,25outro.mp4,45,50CSV ist die Lingua franca tabellarischer Daten: Tabellenkalkulationen, Datenbanken, Kamerasysteme und Webdienste können es exportieren und importieren. Wer Daten “für Excel” bereitstellen oder von dort übernehmen soll, landet fast immer bei CSV.
Naheliegend, aber falsch: selbst parsen
Abschnitt betitelt „Naheliegend, aber falsch: selbst parsen“Der Reflex, CSV mit line.split(",") zu zerlegen, scheitert an den Rändern des Formats: Felder können Kommas enthalten und stehen dann in Anführungszeichen ("Meier, Anna"), Anführungszeichen im Feld werden verdoppelt, Felder können Zeilenumbrüche enthalten. Ein handgeschriebener Parser zerlegt solche Dateien falsch, und zwar erst dann, wenn die ersten echten Daten eintreffen. Python bringt deshalb das Modul csv mit, das alle diese Fälle korrekt behandelt.
Lesen und Schreiben mit dem csv-Modul
Abschnitt betitelt „Lesen und Schreiben mit dem csv-Modul“Am angenehmsten ist die Arbeit mit DictReader und DictWriter, die jede Zeile als Dictionary mit den Spaltennamen aus der Kopfzeile behandeln:
import csv
# reading: every row becomes a dictionarywith open("clips.csv", encoding="utf-8", newline="") as file: reader = csv.DictReader(file) clips = list(reader)
print(clips[0])# {'filename': 'intro.mp4', 'duration_seconds': '90', 'fps': '25'}
# writingclips.append({"filename": "credits.mp4", "duration_seconds": "30", "fps": "25"})
with open("clips.csv", "w", encoding="utf-8", newline="") as file: writer = csv.DictWriter(file, fieldnames=["filename", "duration_seconds", "fps"]) writer.writeheader() writer.writerows(clips)Zwei Fallstricke verdienen einen eigenen Hinweis:
- Alles ist String. CSV kennt keine Datentypen;
duration_secondskommt als'90'an, nicht als90. Wer damit rechnen will, wandelt explizit um:int(clip["duration_seconds"]). Vergessene Umwandlungen fallen erst beim Rechnen oder Sortieren auf (Strings sortieren"9" > "845"). newline=""gehört bei CSV-Dateien zu jedemopen(). Ohne diesen Parameter entstehen unter Windows Leerzeilen zwischen den Datensätzen; das ist eine dokumentierte Eigenheit des Zusammenspiels voncsv-Modul und Zeilenende-Übersetzung.
Ein dritter Stolperstein kommt aus der Praxis im deutschsprachigen Raum: Tabellenkalkulationen mit deutschen Ländereinstellungen exportieren häufig mit Semikolon statt Komma als Trennzeichen (weil das Komma als Dezimaltrennzeichen belegt ist). Das csv-Modul verarbeitet das mit dem Parameter delimiter=";". Wer fremde CSV-Dateien einliest, wirft also zuerst einen Blick in die Datei: Trennzeichen und Kopfzeile sind Teil des Vertrags, und der Vertrag steht nirgendwo sonst.
JSON: strukturierte Daten
Abschnitt betitelt „JSON: strukturierte Daten“CSV bildet flache Tabellen ab. Sobald Daten verschachtelt sind (ein Projekt enthält Clips, jeder Clip hat Marker), braucht es ein Format, das Hierarchien darstellen kann. Der Standard dafür ist JSON (JavaScript Object Notation): das Austauschformat des Webs, von praktisch jeder Programmiersprache unterstützt und auch als Konfigurations- und Projektdateiformat weit verbreitet.
{ "project": "image film", "fps": 25, "clips": [ { "filename": "intro.mp4", "duration_seconds": 90, "markers": [12.5, 47.0, 80.2] }, { "filename": "interview.mp4", "duration_seconds": 845, "markers": [] } ]}JSON kennt sechs Werttypen, und sie entsprechen fast eins zu eins den Python-Grundtypen:
| JSON | Python |
|---|---|
object {...} | dict |
array [...] | list |
| string | str |
| number | int / float |
true / false | True / False |
null | None |
Anders als CSV ist JSON also typisiert: Die 90 im Beispiel ist eine Zahl, kein String. Zu beachten sind die Unterschiede in der Schreibweise: JSON verlangt doppelte Anführungszeichen um Schlüssel und Strings, schreibt true klein und kennt keine Kommentare und kein Komma nach dem letzten Element.
Lesen und Schreiben mit dem json-Modul
Abschnitt betitelt „Lesen und Schreiben mit dem json-Modul“import json
# reading: the whole file becomes nested dicts and listswith open("project.json", encoding="utf-8") as file: project = json.load(file)
print(project["clips"][0]["markers"]) # [12.5, 47.0, 80.2]
# modifying and writing backproject["clips"][0]["markers"].append(85.0)
with open("project.json", "w", encoding="utf-8") as file: json.dump(project, file, indent=2, ensure_ascii=False)json.load() liefert die komplette Struktur als verschachtelte Dictionaries und Listen; der Zugriff funktioniert mit den bekannten Mitteln, ohne neue Konzepte. Beim Schreiben sorgt indent=2 für eingerückte, menschenlesbare Ausgabe, und ensure_ascii=False erhält Umlaute als Umlaute statt als ä-Sequenzen.
Eigene Objekte speichern
Abschnitt betitelt „Eigene Objekte speichern“json.dump() kann mit eigenen Klassen nichts anfangen; es kennt nur die Typen der Tabelle oben. Das Standardmuster ist ein Methodenpaar, das zwischen Objekt und Dictionary übersetzt:
class Clip: def __init__(self, filename, duration_seconds, markers=None): self.filename = filename self.duration_seconds = duration_seconds self.markers = markers if markers is not None else []
def to_dict(self): return { "filename": self.filename, "duration_seconds": self.duration_seconds, "markers": self.markers, }
@classmethod def from_dict(cls, data): return cls(data["filename"], data["duration_seconds"], data["markers"])
clips = [Clip("intro.mp4", 90), Clip("outro.mp4", 45, [3.0])]
with open("clips.json", "w", encoding="utf-8") as file: json.dump([clip.to_dict() for clip in clips], file, indent=2)
with open("clips.json", encoding="utf-8") as file: loaded = [Clip.from_dict(entry) for entry in json.load(file)](@classmethod markiert eine Methode, die auf der Klasse statt auf einem Objekt aufgerufen wird und hier als benannter Konstruktor dient.) Damit ist der Kreis zur Objektorientierung geschlossen: Das Programm arbeitet intern mit Objekten samt Methoden und Kapselung; an der Daten-Schnittstelle werden sie in das neutrale Austauschformat übersetzt und zurück.
Robustheit: Wenn die Datei nicht hält, was sie verspricht
Abschnitt betitelt „Robustheit: Wenn die Datei nicht hält, was sie verspricht“Daten-Schnittstellen haben eine unangenehme Eigenschaft: Die andere Seite des Vertrags ist nicht unter eigener Kontrolle. Dateien fehlen, sind halb geschrieben, von Hand editiert oder schlicht kaputt. Ein Programm, das fremde Dateien einliest, behandelt deshalb drei Fehlerklassen (die Werkzeuge dazu liefert Kapitel 7):
def load_project(path): """Load a project file, raising ProjectFileError with a clear message.""" try: with open(path, encoding="utf-8") as file: data = json.load(file) except FileNotFoundError: raise ProjectFileError(f"project file not found: {path}") except json.JSONDecodeError as error: raise ProjectFileError(f"not valid JSON: {path} (line {error.lineno})")
if "clips" not in data: raise ProjectFileError(f"missing 'clips' entry in: {path}") return data- Die Datei fehlt (
FileNotFoundError): je nach Kontext ein normaler Fall (noch kein Speicherstand) oder ein Fehler. - Die Datei ist kein gültiges JSON (
json.JSONDecodeError): Die Exception nennt sogar die Zeile des Syntaxfehlers, und eine gute Fehlermeldung reicht diese Information weiter. - Das JSON ist gültig, aber inhaltlich falsch: Der Vertrag umfasst mehr als die Syntax; erwartete Schlüssel und Wertebereiche müssen geprüft werden, bevor das Programm damit rechnet.
Die Funktion übersetzt alle drei Fälle in eine eigene, fachliche Exception (Kapitel 7) mit präziser Meldung. Der Aufrufer behandelt damit einen einzigen Fehlertyp, und die Fehlermeldungen taugen für Endbenutzer. Genau diese Sorgfalt an der Datengrenze unterscheidet ein robustes Programm von einem, das bei der ersten unerwarteten Datei mit einem Traceback abstürzt; im Abschlussprojekt ist sie Teil der Anforderungen.
CSV oder JSON?
Abschnitt betitelt „CSV oder JSON?“| Kriterium | CSV | JSON |
|---|---|---|
| Datenform | flache Tabelle | beliebig verschachtelt |
| Datentypen | keine (alles String) | Zahlen, Strings, Bool, null |
| Zielgruppe | Tabellenkalkulation, Datenimport | Programme, Web-APIs, Konfiguration |
| Lesbarkeit für Laien | hoch (in Excel öffnen) | mittel |
| Standardisierung | schwach (Trennzeichen, Encoding variieren) | streng |
Faustregel: Tabellen für Menschen und Tabellenkalkulationen als CSV, strukturierte Daten zwischen Programmen als JSON. Häufig braucht ein Programm beides, etwa JSON als internes Speicherformat und CSV als Exportfunktion für Auswertungen.
Lernergebnisse: Was Sie nach diesem Kapitel können sollten
Abschnitt betitelt „Lernergebnisse: Was Sie nach diesem Kapitel können sollten“Nach Abschluss dieses Kapitels sollten Schülerinnen und Schüler in der Lage sein:
- Anwenden: Textdateien mit
open()imwith-Block lesen und schreiben, mit korrektem Modus und UTF-8-Encoding. - Anwenden: CSV-Dateien mit dem
csv-Modul (DictReader/DictWriter) verarbeiten und die typischen Fallen (Stringtypen, Trennzeichen,newline) vermeiden. - Anwenden: JSON-Dateien mit
json.load/json.dumpverarbeiten und die Typabbildung zwischen JSON und Python nennen. - Entwerfen: eigene Objektstrukturen über
to_dict/from_dictin Austauschformate überführen und zurück. - Erklären: Dateiformate als Daten-Schnittstellen (Verträge zwischen Programmen) einordnen und die Wahl zwischen CSV und JSON begründen.
- Anwenden: Datei- und Formatfehler mit Exceptions robust behandeln und in verständliche Fehlermeldungen übersetzen.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe 17 - Notenverwaltung mit CSV und JSON
- Aufgabe 18 - JSON-Werkstatt: Die Mediendatenbank