mitmario.dev

CSV lesen

Python Sandbox 4 Min Lesezeit 5 BeispieleLektion 1 von 8

Eine Tabelle aus einer Tabellenkalkulation, aus dem Online-Banking oder aus der Vereinsverwaltung kommt fast immer als CSV (comma-separated values, durch Kommas getrennte Werte) heraus. Eine Zeile je Tabellenzeile, die Spalten durch ein Trennzeichen getrennt, und die erste ist die Zeile mit den Spaltennamen (header row).

Das erinnert an das Haushaltsbuch in ausgaben.txt. Eine Zeile je Eintrag, ein Semikolon zwischen den Feldern, zerlegt mit split(";"). Nur hat diese Dateien der Kurs selbst geschrieben. Eine fremde Datei hält sich an Abmachungen, die split nicht kennt.

Das Komma im Namen stimmt in Deutschland meistens nicht. Hier steckt es schon in den Zahlen, 3,50, und deshalb trennen deutsche Tabellenkalkulationen die Spalten mit einem Semikolon. Für split(";") sieht so eine Datei aus wie bisher. Ganz so einfach ist es aber nicht.

Warum split nicht reicht

Zerlegt mit split
with open("mitglieder.csv", encoding="utf-8") as datei:
    for zeile in datei:
        teile = zeile.strip().split(";")
        print(teile)
        nummer, name, abteilung, bemerkung = teile

Karl Weber hat eine Bemerkung, und darin steht selbst ein Semikolon. Damit es nicht für eine Spaltengrenze gehalten wird, steht das ganze Feld in Anführungszeichen, "Kassenwart; zahlt bar". So ist es bei CSV abgemacht, und jede Tabellenkalkulation hält sich daran.

split weiß davon nichts und schneidet auch dort, wo ein Semikolon zwischen Anführungszeichen steht. Heraus kommen fünf Teile statt vier, und die Anführungszeichen hängen noch dran. Beim Verteilen auf vier Namen endet das Programm mit ValueError: too many values to unpack (expected 4, got 5).

Man könnte jetzt anfangen, Anführungszeichen selbst zu zählen. Das geht eine Weile gut. Bis eine Bemerkung kommt, in der selbst ein Anführungszeichen steht.

Das Modul csv

Mit dem Modul csv
import csv

with open("mitglieder.csv", encoding="utf-8", newline="") as datei:
    for zeile in csv.reader(datei, delimiter=";"):
        print(zeile)

Für CSV hat Python ein eigenes Modul dabei, csv, und das kennt die Abmachung. csv.reader bekommt die geöffnete Datei und mit delimiter=";" das Trennzeichen (delimiter). Ohne die Angabe nähme es das Komma.

Jede Zeile kommt als Liste heraus. Karls Bemerkung ist ein einziges Feld, ohne Anführungszeichen und mit dem Semikolon darin. Auch das leere Feld bei Mia ist da, als leerer Text. Und wenn in einem Feld selbst ein Anführungszeichen steht, wird es von der Tabellenkalkulation doppelt geschrieben, "", und csv macht daraus wieder eins.

Neu beim Öffnen ist newline="". Ohne die Angabe übersetzt Python die Zeilenenden schon beim Lesen. Darum will sich aber das Modul csv selbst kümmern, denn zwischen Anführungszeichen darf sogar ein Zeilenumbruch stehen. So steht es in der Python-Doku, und beim Schreiben wird daraus ein Unterschied, den man in Excel sieht.

Für csv.reader ist die erste Zeile eine Zeile wie jede andere, nur eben die mit den Spaltennamen. Wenn du nur die Mitglieder willst, musst du sie selbst überspringen und dir merken, dass der Name an Position 1 steht.

Jede Zeile ein Dictionary

Jede Zeile ein Dictionary
import csv

with open("mitglieder.csv", encoding="utf-8", newline="") as datei:
    for mitglied in csv.DictReader(datei, delimiter=";"):
        print(mitglied["Name"], "spielt", mitglied["Abteilung"])

print(mitglied)

Bequemer ist csv.DictReader. Er liest die erste Zeile selbst und nimmt ihre Felder als Schlüssel. Jede weitere Zeile kommt dann als Dictionary heraus, und du schreibst mitglied["Name"] statt teile[1]. Das ist die Tabelle als Liste von Dictionaries aus Lektion 7.4, nur dass du sie nicht mehr selbst bauen musst.

Es liest sich auch besser. In einem halben Jahr weiß niemand mehr, was an Position 2 stand, aber „Abteilung“ erklärt sich selbst. Und wenn in der Tabelle eine Spalte dazukommt, rutscht nichts.

Eines bleibt wie bei split. Alle Werte sind Text, auch die Nummer, '103' in Anführungszeichen. Zum Rechnen braucht es erst eine Umwandlung. Wie das mit deutschen Beträgen und Daten geht, kommt in der nächsten Lektion.

Ein unsichtbares Zeichen

Ein Spaltenname, der fehlt
import csv

with open("mitglieder.csv", encoding="utf-8", newline="") as datei:
    leser = csv.DictReader(datei, delimiter=";")
    print(leser.fieldnames)
    for mitglied in leser:
        print(mitglied["Nummer"], mitglied["Name"])

Diese mitglieder.csv kommt aus Excel, gespeichert über „Speichern unter“ als „CSV UTF-8“. Sie sieht aus wie vorher. Trotzdem endet das Programm mit KeyError: 'Nummer', obwohl „Nummer“ sichtbar in der ersten Zeile steht. Den Grund siehst du in der Zeile darüber. Dort heißt der erste Spaltenname '\ufeffNummer'.

\ufeff ist das BOM (byte order mark). Das sind drei Bytes ganz am Anfang der Datei, die sagen, dass die Datei UTF-8 ist. Excel schreibt sie, damit beim Öffnen klar ist, wie die Datei gelesen werden muss, auch für Excel selbst. Im Text sieht man das BOM nicht, auch nicht im Editor. Mit encoding="utf-8" gelesen hängt es aber am ersten Spaltennamen.

Mit utf-8-sig gelesen
import csv

with open("mitglieder.csv", encoding="utf-8-sig", newline="") as datei:
    leser = csv.DictReader(datei, delimiter=";")
    print(leser.fieldnames)
    for mitglied in leser:
        print(mitglied["Nummer"], mitglied["Name"])

Was hilft, ist eine andere Kodierung, encoding="utf-8-sig" (sig für signature, die Unterschrift am Anfang). Damit wird UTF-8 gelesen wie bisher, und ein BOM am Anfang fällt weg, wenn eins da ist. Ist keins da, schadet es auch nicht.

Deshalb passt utf-8-sig für jede CSV, die man liest. Ob eine Datei ein BOM hat, sieht man ihr nicht an, und das will man nicht erst herausfinden, wenn es knallt.

Wenn man in Excel statt „CSV UTF-8“ nur „CSV (Trennzeichen-getrennt)“ wählt, kommt übrigens eine Datei in der alten Windows-Kodierung cp1252 aus Lektion 11.2 heraus. Dann hilft auch utf-8-sig nicht, und es heißt encoding="cp1252". Besser ist es, die Datei noch einmal als „CSV UTF-8“ zu speichern.

Was die Aufgabe will

Der Vorstand möchte eine Telefonliste der aktiven Mitglieder. Ein Programm dafür gibt es schon. Es zerlegt die Zeilen mit split und stolpert über eine Telefonnummer mit Semikolon. Mit dem Modul csv kommt es an dieser Zeile vorbei, und die Datei stammt aus Excel.

Zum Mitnehmen

Mit csv.DictReader(datei, delimiter=";") wird eine Tabelle Zeile für Zeile gelesen, jede Zeile als Dictionary mit den Spaltennamen als Schlüsseln, und Felder in Anführungszeichen kommen richtig heraus. Geöffnet wird die Datei mit encoding="utf-8-sig" und newline="". Durch utf-8-sig verschwindet das unsichtbare BOM am Dateianfang, das sonst am ersten Spaltennamen hängt.

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 5 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.