Abschnitt 12 · Lektion 3
CSV schreiben
Die Kassenwartin eines Vereins will die Liste der Beiträge als Tabelle haben, die sie in Excel oder LibreOffice weiterbenutzen kann. Sortieren, ausdrucken, verschicken. Im Terminal nützt ihr die Liste nichts.
Nach dem Lesen geht es also in die andere Richtung. Ein Programm hat etwas ausgerechnet, und das Ergebnis soll in eine Datei. Eine CSV-Datei ist dafür genau richtig, weil jede Tabellenkalkulation sie öffnen kann.
Von Hand geschrieben
from pathlib import Path
mitglieder = [
{"Name": "Mia Lorenz", "Beitrag": 60.0, "Bemerkung": ""},
{"Name": "Karl Weber", "Beitrag": 30.0, "Bemerkung": "Kassenwart; zahlt bar"},
{"Name": "Jörg Schäfer", "Beitrag": 45.5, "Bemerkung": "neu seit Juli"},
]
with open("beitraege.csv", "w", encoding="utf-8") as datei:
datei.write("Name;Beitrag;Bemerkung\n")
for mitglied in mitglieder:
datei.write(f"{mitglied['Name']};{mitglied['Beitrag']};{mitglied['Bemerkung']}\n")
print(Path("beitraege.csv").read_text(encoding="utf-8"), end="") Mit dem Schreiben aus Lektion 11.3 geht das schon. Jede Zeile wird als Text zusammengesetzt und mit
write in die Datei geschrieben. Das Ergebnis hat aber zwei Fehler.
Karls Bemerkung enthält ein Semikolon, und eine Tabellenkalkulation macht daraus zwei Spalten,
„Kassenwart“ und „ zahlt bar“. Seine Zeile hat damit eine Spalte zu viel. Außerdem stehen die
Beiträge mit Punkt da, 30.0. Eine deutsche Tabellenkalkulation hält das nicht für eine Zahl. Im
schlechtesten Fall wird in Excel sogar ein Datum daraus, aus 3.99 etwa der März 99.
csv.DictWriter
import csv
from pathlib import Path
mitglieder = [
{"Name": "Mia Lorenz", "Beitrag": 60.0, "Bemerkung": ""},
{"Name": "Karl Weber", "Beitrag": 30.0, "Bemerkung": "Kassenwart; zahlt bar"},
{"Name": "Jörg Schäfer", "Beitrag": 45.5, "Bemerkung": "neu seit Juli"},
]
with open("beitraege.csv", "w", encoding="utf-8-sig", newline="") as datei:
schreiber = csv.DictWriter(datei, fieldnames=["Name", "Beitrag", "Bemerkung"], delimiter=";")
schreiber.writeheader()
for mitglied in mitglieder:
schreiber.writerow(mitglied)
print(Path("beitraege.csv").read_text(encoding="utf-8-sig"), end="") Das Modul csv kann auch schreiben. csv.DictWriter bekommt die geöffnete Datei, mit fieldnames
die Spalten in ihrer Reihenfolge und mit delimiter=";" das Trennzeichen. writeheader() schreibt
die Zeile mit den Spaltennamen (header heißt Kopf).
writerow() schreibt ein Dictionary als eine Zeile, jeden Wert in die Spalte seines Schlüssels.
Fehlt ein Schlüssel, bleibt das Feld leer. Steht einer zu viel darin, endet das Programm mit einem
ValueError.
Karls Bemerkung steht jetzt in Anführungszeichen, und das Semikolon darin trennt nichts mehr. Darum
kümmert sich DictWriter selbst, genau wie der DictReader beim Lesen. Für Zeilen als Listen statt
Dictionaries gibt es csv.writer, und dort bekommt writerow eine Liste.
Die Beiträge sind allerdings immer noch amerikanisch. DictWriter schreibt eine Zahl so, wie Python
sie schreibt.
Drei Angaben, damit Excel mitspielt
import csv
mitglieder = [
{"Name": "Mia Lorenz", "Beitrag": 60.0, "Bemerkung": ""},
{"Name": "Karl Weber", "Beitrag": 30.0, "Bemerkung": "Kassenwart; zahlt bar"},
{"Name": "Jörg Schäfer", "Beitrag": 45.5, "Bemerkung": "neu seit Juli"},
]
with open("beitraege.csv", "w", encoding="utf-8-sig", newline="") as datei:
schreiber = csv.DictWriter(datei, fieldnames=["Name", "Beitrag", "Bemerkung"], delimiter=";")
schreiber.writeheader()
for mitglied in mitglieder:
beitrag = f"{mitglied['Beitrag']:.2f}".replace(".", ",")
schreiber.writerow({"Name": mitglied["Name"], "Beitrag": beitrag, "Bemerkung": mitglied["Bemerkung"]})
# Mit utf-8 statt utf-8-sig und newline="" kommt der Inhalt so zurück, wie
# er in der Datei steht, samt BOM und Zeilenenden.
with open("beitraege.csv", encoding="utf-8", newline="") as datei:
print(repr(datei.read())) Diesmal kommen die Beiträge vorher als Text hinein, mit zwei Nachkommastellen und einem Komma, also
f"{beitrag:.2f}".replace(".", ","). Tausenderpunkte bleiben in einer CSV weg. Die sind nur zum
Lesen da, und ein Programm, das die Datei später weiterverarbeitet, stolpert darüber, so wie
float() in Lektion 6.4 aus 3.000 eine Drei gemacht hat.
Am Ende wird die Datei so zurückgelesen, wie sie wirklich ist, und mit repr() siehst du alles, was
sonst unsichtbar bleibt. Ob Excel die Datei sauber öffnet oder Zeichensalat zeigt, hängt an drei
Dingen.
delimiter=";", weil eine deutsche Tabellenkalkulation das Semikolon als Trennzeichen erwartet. Mit dem Komma, dascsvohne Angabe nimmt, landet jede Zeile in einer einzigen Spalte.encoding="utf-8-sig". Das ist das\ufeffganz vorn, das BOM vom Lesen. Beim Schreiben setztutf-8-siges an den Anfang. Ohne BOM öffnet Excel unter Windows eine CSV per Doppelklick in der alten Windows-Kodierung, und aus Jörg Schäfer wirdJörg Schäfer. Mit BOM ist klar, dass die Datei UTF-8 ist.newline="". Am Ende jeder Zeile steht\r\n, zwei Zeichen, so wie Windows Zeilen beendet. Das schreibtcsvselbst, auf jedem Rechner. Ohnenewline=""mischt sich Python zusätzlich ein.
import csv
# newline="\r\n" macht hier, was Windows ohne jede Angabe von selbst tut:
# Aus jedem \n wird beim Schreiben ein \r\n.
with open("beitraege.csv", "w", encoding="utf-8-sig", newline="\r\n") as datei:
schreiber = csv.writer(datei, delimiter=";")
schreiber.writerow(["Name", "Beitrag"])
schreiber.writerow(["Mia Lorenz", "60,00"])
with open("beitraege.csv", encoding="utf-8-sig", newline="") as datei:
print(repr(datei.read()))
with open("beitraege.csv", encoding="utf-8-sig", newline="") as datei:
for zeile in csv.reader(datei, delimiter=";"):
print(zeile) Unter Windows macht Python beim Schreiben ohne jede Angabe aus jedem \n ein \r\n. Aus dem \r\n
von csv wird so \r\r\n. Im Beispiel ist das mit newline="\r\n" nachgestellt, weil dein Code
hier auf Linux läuft, und dort passiert das nicht.
Ein Programm, das die Datei zurückliest, findet dann hinter jeder Zeile eine leere, und in Excel
steht unter jeder Zeile eine leere Zeile. Mit newline="" lässt Python die Zeilenenden in Ruhe, und
csv hat das letzte Wort. Obwohl du den Fehler auf deinem Rechner nur siehst, wenn er unter Windows
läuft, steht newline="" auch unter Linux immer da.
Was die Aufgabe will
Für die Steuererklärung braucht es alle Rechnungen von Handwerkern aus einem Jahr Kontoauszug, als Tabelle, die man der Steuerberaterin schicken kann. Das Programm findet die Buchungen schon und gibt sie im Terminal aus. Was fehlt, ist eine Datei, die Excel ohne Murren öffnet.
Zum Mitnehmen
csv.DictWriter(datei, fieldnames=[...], delimiter=";") schreibt mit writeheader() die Zeile mit den Spaltennamen und mit writerow() aus jedem Dictionary eine Zeile. Ein Feld mit Semikolon landet dabei in Anführungszeichen. Damit Excel die Datei sauber öffnet, braucht es beim Öffnen encoding="utf-8-sig" und newline="", und die Beträge stehen mit Komma drin.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.
Was in dieser Lektion steckt
-
Artikel mit 4 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe, dein Code läuft auf einem Server
Öffnet sich mit dem Basis Konto.