ubuntuusers.de

Python - Log / CSV im Nachhinein bearbeiten

Status: Gelöst | Ubuntu-Version: Ubuntu 20.04 (Focal Fossa)
Antworten |

jAIk

Avatar von jAIk

Anmeldungsdatum:
7. Juni 2007

Beiträge: 254

Hallo zusammen,

ich versuche mein Szenario mal kurz an einem Beispiel zu abstrahieren:

Mein Python schreibt aktuell Daten in eine CSV Datei, die ungefähr so aussieht:

Timestamp, Parameter1, Parameter2, Parameter3,
2020-11-00, Apfel, Kiwi, Banane,
2020-11-01, Apfel, Birne, Banane,

Nun soll ein weiteres Feld "Lecker_ja_nein" in mein Log aufgenommen werden, das initial None beinhaltet:

Timestamp, Parameter1, Parameter2, Parameter3,Lecker_ja_nein,
2020-11-01, Apfel, Kiwi, Banane, None,
2020-11-02, Apfel, Kiwi, Banane, None,
2020-11-03, Apfel, Birne, Banane, None,

Meine Problemstellung: Nachdem weitere Log-Eintäge hinzu gekommen sind, möchte ich das Feld zu einem späteren Zeitpunkt befüllen. Um in diesem Beispiel zu bleiben: Der letzten Zeile, die die Parameter "Apfel, Kiwi, Banane" beinhaltet, soll im Feld "Lecker_ja_nein" ein "ja" hinzugefügt werden.

Da wir in meinem Programm über ein paar hundert Zeilen Log-File sprechen könnte das Programm problemlos wie folgt ablaufen:

1
2
3
4
5
6
7
8
9
###Python Pseudocode
with open('log.csv', newline='') as csvfile:
    i = -1
    while True:
        row = PSEUDOCODE_REIHEN_MEINER_CSV_FILE[i]
        if row[1] == "Apfel" and row[2] == "Kiwi" and row[3] == "Banane" and row[4] == Null:
            row[4] == "ja"
            break
        i -= 1

Das Ganze würde vermutlich funktionieren - sieht für mich als Anfänger aber recht unrund aus. Würdet ihr hier ähnlich vorgehen wie ich oder habt ihr vielleicht eine galantere Lösung?

Beste Grüße jak

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11250

Wohnort: München

Bei CSV verzichtet man normalerweise auf einen Space nach dem Delimiter, der macht einem das Leben nur schwerer und man hängt auch nicht einfach so einen Delimiter ans Ende an, denn das definiert ja bereits eine neue Spalte - und lange Strings, die ein leeres Feld signalisieren braucht man nicht.

Die Datei hätte also von Anfang an besser so aussehen sollen:

Timestamp,Parameter1,Parameter2,Parameter3
2020-11-00,Apfel,Kiwi,Banane
2020-11-01,Apfel,Birne,Banane

Und mit der neuen Spalte reicht es dann jeweils einen Delimiter anzuhängen, damit ist klar, dass noch kein Wert in der Spalte vorhanden ist:

Timestamp,Parameter1,Parameter2,Parameter3,Lecker_ja_nein
2020-11-00,Apfel,Kiwi,Banane,
2020-11-01,Apfel,Birne,Banane,

Wenn man dann später das ganze um weitere Spalten ergänzt, sieht man die leeren Felder an den aufeinander folgenden Delimitern:

Timestamp,Parameter1,Parameter2,Parameter3,Lecker_ja_nein,foo
2020-11-00,Apfel,Kiwi,Banane,,       # Felder Lecker_ja_nein und foo sind leer
2020-11-01,Apfel,Birne,Banane,ja,    # Feld foo ist leer
2020-11-02,Apfel,Mango,Banane,,bar   # Feld Lecker_ja_nein ist leer
2020-11-02,Apfel,Mango,Banane,ja,bar # alle Felder befüllt

Null gibt es in Python nicht, aber None - wenn es aus einer CSV-Datei gelesen wurde, hast du aber erst mal nur einen String "None".

Das mit dem i als Index ist nicht besonders pythonisch - du kannst die while-Schleife durch eine for-Schleife ersetzen und komplett auf den Index verzichten (ist auch schneller) - und um die Datei in einerm Rutsch zu ändern (im Hintergrund wird die Datei umbenannt und die Datei neu angelegt und später die temporäre Datei gelöscht), könnte man z.B. sowas machen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import csv
import fileinput
import sys

with fileinput.input(files=('log.csv'), inplace=True, mode='r') as f:
    reader = csv.DictReader(f, delimiter=',', skipinitialspace=True)
    if 'Lecker_ja_nein' not in reader.fieldnames:
        reader.fieldnames = ['Timestamp', 'Parameter1', 'Parameter2', 'Parameter3', 'Lecker_ja_nein', *reader.fieldnames[5:]]
    sys.stdout.reconfigure(newline='')
    writer = csv.DictWriter(sys.stdout, delimiter=',', fieldnames=reader.fieldnames)
    writer.writeheader()
    for row in reader:
        if row['Lecker_ja_nein'] == 'None':  # empty cells should be empty strings
            row['Lecker_ja_nein'] = ''
        if (row['Parameter1'], row['Parameter2'], row['Parameter3'], row['Lecker_ja_nein']) == ('Apfel', 'Kiwi', 'Banane', ''):
            row['Lecker_ja_nein'] = 'ja'
        writer.writerow(row)

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17605

Wohnort: Berlin

Die Bezeichnung "row" ist übrigens irreführend. Was hier adressiert wird ist eine "col" (column).

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11250

Wohnort: München

Die Werte der Felder der einzelnen Spalten (columns) lassen sich mit einem csv.DictReader nur jeweils für eine Reihe (Row) auslesen - vgl. https://docs.python.org/3/library/csv.html#csv.DictReader.

jAIk

(Themenstarter)
Avatar von jAIk

Anmeldungsdatum:
7. Juni 2007

Beiträge: 254

Ich bitte die verspätete Antwort zu entschuldigen...

Vielen Dank! Genau das, was ich gesucht habe.

VG jaik

jAIk

(Themenstarter)
Avatar von jAIk

Anmeldungsdatum:
7. Juni 2007

Beiträge: 254

...ich nochmal.

Die Lösung funktioniert. Einzige Problematik: Sonderzeichen (ä,ö,ü usw.) werden nicht erkannt, da ein "falsches" Encoding eingestellt ist.

Nachgeschaut: Es wird empfohlen, die Datei mit einem openhook (in meinem Fall mit dem Encoding utf-8) zu öffnen. Dies ist aber nicht möglich, sofern inplace=True aktiviert ist:

1
2
3
4
with fileinput.input(files=(filename), openhook=fileinput.hook_encoded("utf-8"), inplace=True, mode='r') as f:
...

>>> ValueError: FileInput cannot use an opening hook in inplace mode

Die Lösungen dazu sehen aus meiner unerfahrenen Sichtweise alle etwas krude aus und gehe z.B. in diese Richtung: https://stackoverflow.com/questions/25203040/combining-inplace-filtering-and-the-setting-of-encoding-in-the-fileinput-module

Würdet ihr das auch so machen oder gibt es hier vielleicht noch einen besseren Weg?

seahawk1986

Anmeldungsdatum:
27. Oktober 2006

Beiträge: 11250

Wohnort: München

jAIk schrieb:

Die Lösung funktioniert. Einzige Problematik: Sonderzeichen (ä,ö,ü usw.) werden nicht erkannt, da ein "falsches" Encoding eingestellt ist.

Wer hat da wo ein anderes Encoding als UTF-8 genutzt?

Nachgeschaut: Es wird empfohlen, die Datei mit einem openhook (in meinem Fall mit dem Encoding utf-8) zu öffnen. Dies ist aber nicht möglich, sofern inplace=True aktiviert ist:

1
2
3
4
with fileinput.input(files=(filename), openhook=fileinput.hook_encoded("utf-8"), inplace=True, mode='r') as f:
...

>>> ValueError: FileInput cannot use an opening hook in inplace mode

Python3 nutzt standardmäßig UTF-8 als Encoding für alles - wenn es damit Probleme gibt, hat deine Eingabedatei vermutlich ein anderes Encoding (wenn sie von Windows kommt, vermutlich entweder latin-1(5) oder cp-1252).

Die Lösungen dazu sehen aus meiner unerfahrenen Sichtweise alle etwas krude aus und gehe z.B. in diese Richtung: https://stackoverflow.com/questions/25203040/combining-inplace-filtering-and-the-setting-of-encoding-in-the-fileinput-module

Würdet ihr das auch so machen oder gibt es hier vielleicht noch einen besseren Weg?

Man könnte das z.B. so machen, wenn man die Datei mit einem anderen Encoding öffnen will:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import csv
import io
import os
import tempfile

FILENAME = 'log.csv'

with open(FILENAME, encoding='latin-1', newline='') as f:
    buffer = io.StringIO(f.read(), newline='')
reader = csv.DictReader(buffer, delimiter=',', skipinitialspace=True)
if 'Lecker_ja_nein' not in reader.fieldnames:
    reader.fieldnames = ['Timestamp', 'Parameter1', 'Parameter2', 'Parameter3', 'Lecker_ja_nein', *reader.fieldnames[5:]]

with tempfile.NamedTemporaryFile(mode='w', encoding='utf-8', delete=False, newline='') as output:
    writer = csv.DictWriter(output, delimiter=',', fieldnames=reader.fieldnames)
    writer.writeheader()
    for row in reader:
        if row['Lecker_ja_nein'] == 'None':  # empty cells should be empty strings
            row['Lecker_ja_nein'] = ''
        if (row['Parameter1'], row['Parameter2'], row['Parameter3'], row['Lecker_ja_nein']) == ('Apfel', 'Kiwi', 'Banane', ''):
            row['Lecker_ja_nein'] = 'ja'
        writer.writerow(row)

# replace the original file with the temporary file
os.rename(output.name, FILENAME)

jAIk

(Themenstarter)
Avatar von jAIk

Anmeldungsdatum:
7. Juni 2007

Beiträge: 254

Läuft. Vielen Dank.

Die CSV stammt in der Tat von einem Windows Gerät...

Antworten |