mitmario.dev

CSV schreiben

Python Sandbox 3 Min Lesezeit 4 BeispieleLektion 3 von 8

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

Von Hand zusammengeklebt
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

Mit 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

Was wirklich in der Datei steht
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, das csv ohne Angabe nimmt, landet jede Zeile in einer einzigen Spalte.
  • encoding="utf-8-sig". Das ist das \ufeff ganz vorn, das BOM vom Lesen. Beim Schreiben setzt utf-8-sig es an den Anfang. Ohne BOM öffnet Excel unter Windows eine CSV per Doppelklick in der alten Windows-Kodierung, und aus Jörg Schäfer wird Jö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 schreibt csv selbst, auf jedem Rechner. Ohne newline="" mischt sich Python zusätzlich ein.
Wie Windows ohne newline schreibt
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, kostenlos

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