Salve
ich bin neu hier und noch nicht sicher, wo ich dieses Thema posten soll
Ich lese in Python ein csv-File ein, welches Kommata als Spaltenseparatoren verwendet.
Das File lese ich zuerst mit .readlines und liefert einzelne Zeilen.
Diese Zeilen teile ich mit split auf
In der ersten Zeile bestimme ich die Spaltenköpfe und die gesuchten Spaltennummern.
Allerdings habe ich in manchen Zeilen Einträge, die in der Spalte auch ein Komma im Eintrag enthalten
Im Debugger werden diese Einträge mit Anführungszeichen angezeigt.
Die Split-Funktion ignoriert mir diese und teilt dies auf.
Beispiel
Zeile: 'Eintrag1,Eintrag2,"Eintrag3, 4",Eintrag5'
Beabsichtigte, gewünschte Aufteilung wäre ( die // zur Verdeutlichung )
Eintrag1 // Eintrag2 // Eintrag3, 4 // Eintrag5
Der Split liefert aber
'Eintrag1 // Eintrag2 // Eintrag3 // 4 // Eintrag5
Wie schaffe ich das, dass der dritte Spalteneintrag als "Eintrag3, 4" nach dem Split erhalten bleibt?
Danke
Thomas
csv mit Separator-Zeichen im Einzeleintrag
-
geraldfo
- User
- Beiträge: 82
- Registriert: Samstag 28. Januar 2023, 20:19
- Wohnort: Nähe Wien
- Kontaktdaten:
Vielleicht hilfreich:
https://docs.python.org/3/library/csv.html
https://docs.python.org/3/library/csv.html
- noisefloor
- User
- Beiträge: 4307
- Registriert: Mittwoch 17. Oktober 2007, 21:40
- Wohnort: WW
- Kontaktdaten:
Hallo,
die Lösung ist: nicht selber machen, sondern das passende Modul verwenden, siehe vorheriger Beitrag. Da kannst du dem CSV Reader auch mitgeben, was der `separator` ist - nämlich bei dir das Komma - und was der `quotechar` ist - nämlich bei dir die Anführungszeichen.
Gruß, noisefloor
die Lösung ist: nicht selber machen, sondern das passende Modul verwenden, siehe vorheriger Beitrag. Da kannst du dem CSV Reader auch mitgeben, was der `separator` ist - nämlich bei dir das Komma - und was der `quotechar` ist - nämlich bei dir die Anführungszeichen.
Gruß, noisefloor
Danke
werde ich testen; hatte ich bisher nur am Rande gesehen, aber beim ersten Lesen immer ein paar Sachen gesehen, die mir da quer schossen, bswp. dass wohl alles per String kommt und manchmal mit umschlossenen Anführungszeichen.
Hat jemand einen Tip für aussagekräftigen Beispielcode?
Danke
thomas
werde ich testen; hatte ich bisher nur am Rande gesehen, aber beim ersten Lesen immer ein paar Sachen gesehen, die mir da quer schossen, bswp. dass wohl alles per String kommt und manchmal mit umschlossenen Anführungszeichen.
Hat jemand einen Tip für aussagekräftigen Beispielcode?
Danke
thomas
- __blackjack__
- User
- Beiträge: 14349
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@Thomas67: Wenn Du die einzelnen Zeilen (= Zeichenketten) selber zerlegst, bekommst Du ja auch Zeichenketten. Und Anführungszeichen in einzelnen Zellen bekommt man nur wenn da auch tatsächlich Anführungszeichen drin sind. Bei Deinem Beispiel sind keine _in_ den Zellen:
Tipp für Beispielcode wäre die Dokumentation vom `csv`-Modul.
Code: Alles auswählen
>>> next(csv.reader(io.StringIO('Eintrag1,Eintrag2,"Eintrag3, 4",Eintrag5', newline="")))
['Eintrag1', 'Eintrag2', 'Eintrag3, 4', 'Eintrag5']“It is easier to optimize correct code than to correct optimized code.” — Bill Harlan
@Thomas67:
Richtig, die einzelnen Felder muss man in die passenden Typen konvertieren. Je nach Programmierstil könnte man diese auch per zip() mit der Ausgabe des Readers kombinieren. Etwas mehr Komfort gibt einem die read_csv()-Funktion der pandas-Bibliothek mit dem dtype-Parameter. Dafür muss man natürlich pandas inkl. numpy installiert haben.
Falls dir Dezimalzahlen in deutscher Schreibweise vorliegen, kannst du das Komma und ggf. den Punkt als Tausendertrenner mittels replace() umwandeln oder atof() aus dem locale-Modul der Standardbibliothek verwenden. Für letzteres müssen aber vorab die Spracheinstellungen (setlocale) auf German eingestellt werden, sonst funktioniert die Umwandlung nicht.
EDIT: Pandas bietet einem auch convert_dtypes für eine automatische Umwandlung der Daten. Das funktioniert allerdings nicht mit den besagten deutschsprachigen Kommazahlen. Deine Angabe aus dem ersten Beitrag lässt entsprechende Vermutungen bei mir aufkommen...
Richtig, die einzelnen Felder muss man in die passenden Typen konvertieren. Je nach Programmierstil könnte man diese auch per zip() mit der Ausgabe des Readers kombinieren. Etwas mehr Komfort gibt einem die read_csv()-Funktion der pandas-Bibliothek mit dem dtype-Parameter. Dafür muss man natürlich pandas inkl. numpy installiert haben.
Falls dir Dezimalzahlen in deutscher Schreibweise vorliegen, kannst du das Komma und ggf. den Punkt als Tausendertrenner mittels replace() umwandeln oder atof() aus dem locale-Modul der Standardbibliothek verwenden. Für letzteres müssen aber vorab die Spracheinstellungen (setlocale) auf German eingestellt werden, sonst funktioniert die Umwandlung nicht.
EDIT: Pandas bietet einem auch convert_dtypes für eine automatische Umwandlung der Daten. Das funktioniert allerdings nicht mit den besagten deutschsprachigen Kommazahlen. Deine Angabe aus dem ersten Beitrag lässt entsprechende Vermutungen bei mir aufkommen...
Nachtrag: read_csv() bietet einem die Parameter thousands und decimal. Die kann man für deutsche Zahlen anpassen. Dann funktioniert auch convert_dtypes() auf Anhieb. Pandas (falls dir noch nicht bekannt) ist ne umfangreiche Bibliothek und wegen der der Numpy-Abhängigkeit auf manchen Systemen nicht ohne Weiteres verfügbar. Aber sie kann eben auch vieles.
- DeaD_EyE
- User
- Beiträge: 1333
- Registriert: Sonntag 19. September 2010, 13:45
- Wohnort: Hagen
- Kontaktdaten:
Wo steht das in der Doku? Hab gerade danach gesucht und nichts gefunden.snafu hat geschrieben: Mittwoch 4. März 2026, 02:59 Nachtrag: read_csv() bietet einem die Parameter thousands und decimal.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
- __blackjack__
- User
- Beiträge: 14349
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
@DeaD_EyE: Das steht in der Dokumentation von der Funktion. Und zwar bis zurück zur Version 1.0, dass heisst die beiden Argumente gab es schon von Anfang an: pandas.read_csv()
“It is easier to optimize correct code than to correct optimized code.” — Bill Harlan
- DeaD_EyE
- User
- Beiträge: 1333
- Registriert: Sonntag 19. September 2010, 13:45
- Wohnort: Hagen
- Kontaktdaten:
Da kann ich in der Python-Doku lange suchen__blackjack__ hat geschrieben: Mittwoch 4. März 2026, 15:51 @DeaD_EyE: Das steht in der Dokumentation von der Funktion. Und zwar bis zurück zur Version 1.0, dass heisst die beiden Argumente gab es schon von Anfang an: pandas.read_csv()
Pandas ist für mich ein rotes Tuch.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
-
Pedroski55
- User
- Beiträge: 46
- Registriert: Freitag 25. Juli 2025, 00:20
Die Lösung ist einfach selber zu machen, und hat den Vorteil, dass man sieht, was da genau ablaüft. Lehrreich!
1. Suche alle Textteile des Formats: Zahl Komma Leertaste Zahl und mache eine Liste.
2. Im Resultat von 1. ersetze jeweils , mit ; (oder sonstwas) und ersetze zB '3, 4' mit '3; 4'
3. Spalte zeile dann an , wie gewollt.
4. Falls gewünscht, entferne " und ; man kann genauso gut so lassen
spalten = sauber_machen(zeile)
Da hat man nun die sauberen Spaltenkopfnamen.
1. Suche alle Textteile des Formats: Zahl Komma Leertaste Zahl und mache eine Liste.
2. Im Resultat von 1. ersetze jeweils , mit ; (oder sonstwas) und ersetze zB '3, 4' mit '3; 4'
3. Spalte zeile dann an , wie gewollt.
4. Falls gewünscht, entferne " und ; man kann genauso gut so lassen
Code: Alles auswählen
import regex as re
from random import randint
import csv
csv_file = '/home/peterr/temp/csvs/eintrag.csv'
zeile = 'Eintrag1,Eintrag2,"Eintrag3, 4",Eintrag5,"Eintrag6, 7",Eintrag8,"Eintrag9, 10",Eintrag11,Eintrag12'
# suche alle Textteile des Formats: Zahl Komma Leertaste Zahl
e = re.compile(r'\d+, \d+')
def sauber_machen(neu_zeile):
res = e.findall(neu_zeile) # ergibt ['3, 4', '6, 7', '9, 10'] hier
for p in res:
neu_p = p.replace(',', ';')
neu_zeile = re.sub(p, neu_p, neu_zeile)
# geht auch so
#neu_zeile = neu_zeile.replace(p, neu_p)
# nun kann man an , spalten
spalten = neu_zeile.split(',')
# entferne ; und die " wenn sie stören
for i in range(len(spalten)):
spalten [i] = spalten [i].replace(';', ',').replace('"', '')
return spaltenDa hat man nun die sauberen Spaltenkopfnamen.
gleich machen wir ein bißchen Inhalt und speichern als csv['Eintrag1', 'Eintrag2', 'Eintrag3, 4', 'Eintrag5', 'Eintrag6, 7', 'Eintrag8', 'Eintrag9, 10', 'Eintrag11', 'Eintrag12']
Code: Alles auswählen
# stelle etwas Inhalt her
data = []
for i in range(10):
row = []
for j in range(9):
num= randint(1, 25)
row. append(num)
data.append(row)
with open(csv_file, 'w', newline='') as cf:
csvwriter = csv.writer(cf, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
csvwriter.writerow(spalten)
csvwriter.writerows(data)@Pedroski55: Nein, bitte nicht selber machen sondern das CSV-Modul benutzen. Dein Code ist ein gutes Beispiel dafür, warum man das auf gar keinen Fall selber machen sollte. Du hast dir da irgendwas zusammengefummelt, was irgendwie zu den Beispieldaten passt, aber schon bei der kleinsten Abweichung nicht funktioniert.
Und du machst hier wieder die selben Fehler, auf die du schon in anderen Threads hingewiesen wurdest. Das ist dann genau das Gegenteil von lehrreich, weil es nicht so wirkt, als würde jemand lernen. Zum Beispiel verwendest du wieder das "regex" Modul, obwohl "re" reichen würde.
@Thomas67: Nochmal zur Bekräftigung: Die Lösung ist nach wie vor das csv Modul.
Code: Alles auswählen
'"1,2",Eintrag1,Eintrag2,"Eintrag3, 4",Eintrag5,"Eintrag6,7",Eintrag8,"Eintrag9,10",Eintrag11,"Eint\"rag12"
['1', '2', 'Eintrag1', 'Eintrag2', 'Eintrag3, 4', 'Eintrag5', 'Eintrag6', '7', 'Eintrag8', 'Eintrag9', '10', 'Eintrag11', 'Eintrag12']'@Thomas67: Nochmal zur Bekräftigung: Die Lösung ist nach wie vor das csv Modul.
@Pedroski55: es ist zwar gut, etwas selbst zu machen, um Dinge zu verstehen. Dann sollte man aber das csv-Format und seine Eigenheiten wirklich verstanden haben. Du hast jetzt einen Spezialfall abgedeckt, der bei kleinen Abweichungen aber wieder zu Fehlern führt. Also nein, nicht selber machen, sondern was fertiges, richtiges, gut getestetes verwenden.
Das regex-Modul wird nicht gebraucht, da man genausogut re verwenden könnte.
`e` ist ein besonders nichtssagender Variablenname, zumal noch global. Globale Konstanten werden komplett groß geschrieben: TWO_NUMBERS_REGEX
`sauber_machen` ist ein schlechter Funktionsname, weil er nicht sagt, was eigentlich gemacht wird, nämlich einen String in Einzelteile zerlegen.
`neu_zeile` als Argumentname ist schlecht, weil der Funktion egal ist, ob sie mit einer neu_zeile oder einer alt_zeile aufgerufen wird.
Und wieder verwendest Du re.sub mit einem nutzerdefinierten String als Regulären Ausdruck, bei dem alles mögliche an Sonderzeichen drin sein könnte. Allein ein * oder ? macht schon die ganze Ersetzung kaputt. Deine auskommentierte Zeile wäre da schon weniger fehleranfällig. Da du aber eh reguläre Ausdrücke einsetzt, hättest Du das ganze Problem gleich damit lösen können.
Über einen Index iteriert man nicht, weil es lesbarer ist, direkt mit den Elementen zu arbeiten; eine Liste verändert man nicht, sondern erzeugt einfach eine neue.
ABER: die ganze Funktion ist sehr Fehleranfällig. Was passiert, wenn tatsächlich ein ; in einer Zelle vorkommt, wenn eine Zelle tatsächlich mit einer Zahl beginnt, oder wenn auch noch Zeichen wie " innerhalb einer Zelle vorkommen? Oder andere Zeichen, die ein Quoting erforderlich machen?
Deshalb gibt es ja das csv-Modul, das all das korrekt behandelt. Weil sich da Leute genau überlegt haben, welche Fälle alles vorkommen können und es genau definiert ist, wie dann reagiert wird.
Da läuft sehr viel mehr ab, als Du glaubst.
Das regex-Modul wird nicht gebraucht, da man genausogut re verwenden könnte.
`e` ist ein besonders nichtssagender Variablenname, zumal noch global. Globale Konstanten werden komplett groß geschrieben: TWO_NUMBERS_REGEX
`sauber_machen` ist ein schlechter Funktionsname, weil er nicht sagt, was eigentlich gemacht wird, nämlich einen String in Einzelteile zerlegen.
`neu_zeile` als Argumentname ist schlecht, weil der Funktion egal ist, ob sie mit einer neu_zeile oder einer alt_zeile aufgerufen wird.
Und wieder verwendest Du re.sub mit einem nutzerdefinierten String als Regulären Ausdruck, bei dem alles mögliche an Sonderzeichen drin sein könnte. Allein ein * oder ? macht schon die ganze Ersetzung kaputt. Deine auskommentierte Zeile wäre da schon weniger fehleranfällig. Da du aber eh reguläre Ausdrücke einsetzt, hättest Du das ganze Problem gleich damit lösen können.
Über einen Index iteriert man nicht, weil es lesbarer ist, direkt mit den Elementen zu arbeiten; eine Liste verändert man nicht, sondern erzeugt einfach eine neue.
Code: Alles auswählen
import re
TWO_NUMBERS_REGEX = re.compile(r"\d+, \d+")
def split_line_with_special_handling_of_comma_separated_numbers(line):
fixed_line = TWO_NUMBERS_REGEX.sub(lambda m: m.group(0).replace(",", ";"), line)
cells= fixed_line.split(",")
return [
cell.replace(";", ",").replace('"', "")
for cell in cells
]Deshalb gibt es ja das csv-Modul, das all das korrekt behandelt. Weil sich da Leute genau überlegt haben, welche Fälle alles vorkommen können und es genau definiert ist, wie dann reagiert wird.
Da läuft sehr viel mehr ab, als Du glaubst.
- __blackjack__
- User
- Beiträge: 14349
- Registriert: Samstag 2. Juni 2018, 10:21
- Wohnort: 127.0.0.1
- Kontaktdaten:
Als spezielles Ersatzzeichen könnte man ein ”Nullbyte” statt Semikolon benutzen. Das sollte in Text nicht vorkommen. Darf also an anderer Stelle nicht schon für irgendeinen Hack verwendet werden bevor die Zeile in diese Funktion kommt.
Mir sind da ein paar zu viele Namen für Zwischenergebnisse drin. Am Ende wird das noch zu verständlich.
Aber letztlich ist das auch ein bisschen fragwürdig mit der Begründung „selber machen“ und „um etwas zu lernen“, auf reguläre Ausdrücke zurück zu greifen, statt das tatsächlich selber zu machen und etwas zu lernen. Beispielsweise über Zustandsautomaten und wie man die in Code umsetzt. Die folgende Tcl-Funktion zerlegt eine Zeile mit gequoteten Zellen die mit ``"`` beginnen und ``",`` enden, ungequotete Zellen die mit ``,`` enden. Innerhalb von Zellen (beide Typen) dürfen ``"`` vorkommen, die erhalten bleiben.
Mir sind da ein paar zu viele Namen für Zwischenergebnisse drin. Am Ende wird das noch zu verständlich.
Code: Alles auswählen
import re
TWO_NUMBERS_REGEX = re.compile(r"\d+, \d+")
def split_line_with_special_handling_of_comma_separated_numbers(line):
assert "\0" not in line
return [
cell.replace("\0", ",").replace('"', "")
for cell in TWO_NUMBERS_REGEX.sub(
lambda match: match[0].replace(",", "\0"), line
).split(",")
]Code: Alles auswählen
proc splitLine {line {delimiter ,} {quoteChar \"}} {
set row {}
set state CELL_START
set i 0
set j 0
for {set i 0} {$i < [string length $line]} {incr i} {
set character [string index $line $i]
if {$state eq "CELL_START"} {
if {$character eq $delimiter} {
lappend row ""
set j [expr $i + 1]
} elseif {$character eq $quoteChar} {
set j [expr $i + 1]
set state QUOTED_CELL
} else {
set j $i
set state UNQUOTED_CELL
}
} elseif {$state eq "UNQUOTED_CELL"} {
if {$character eq $delimiter} {
lappend row [string range $line $j [expr $i - 1]]
set j [expr $i + 1]
set state CELL_START
}
} elseif {$state eq "QUOTED_CELL"} {
if {$character eq $quoteChar} {
set nextCharacter [string index $line [expr $i + 1]]
if {$nextCharacter eq $delimiter || $nextCharacter eq ""} {
lappend row [string range $line $j [expr $i - 1]]
set state QUOTED_CELL_END
}
}
} elseif {$state eq "QUOTED_CELL_END"} {
set j [expr $i + 1]
set state CELL_START
}
}
if {$state ne "QUOTED_CELL_END"} {
lappend row [string range $line $j end]
}
return $row
}“It is easier to optimize correct code than to correct optimized code.” — Bill Harlan
