ein .csv-tamplate beschreiben und als neue Datei abspeichern

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Minzent
User
Beiträge: 16
Registriert: Dienstag 11. September 2018, 15:09

Guten Tag liebe Community,

zur Zeit versuche ich mein bisher geschriebenes Programm für einen Batterieprüfstand um eine logging-Funktion zu erweitern. Heißt letztlich: Ich möchte, dass mir das Programm während des Ladevorgangs einer Batterie meine Meßwerte in eine .csv-Datei schreibt und bei Programmende bzw. bei KeayboardInterrupt diese CSV mit dem Dateiname DATUM/UHRZEIT.csv abspeichert. Es soll also jedes mal eine neue Datei erstellt werden.

Am liebsten wäre mir, wenn ich mein selbst erstelltes template mit dem Namen 'logging_template.csv' öffnen, an den richtigen Stellen befüllen, und dann eben als neue Datei mit dem erwähnten Namen abspeichern kann.

Das Template hat zB diese Form (die roten einträge & die Measuring No. werden dann bei Programmlauf beschrieben):
[URL=https://www.bilder-upload.eu/bild-5dab7 ... 4.jpg.html]

Mein auf dieses Problem gekürzte Skript sieht folgendermaßen aus:

Code: Alles auswählen

pRequest = Request()
from_config = config()
charge_voltage, charge_ampere = pRequest.charge_request()

try:

	with open('logging_template.csv','a') as datafile:
		datafile.write(';' + time.asctime(time.localtime()) + '\n\n' + ';' + str(from_config.ABTASTFREQUENZ))

	i=0 #Measuring Point No.
	while charge_voltage < 40:

		with open('logging_template.csv','a') as datafile:
			datafile.write('\n' + ';;;' + str(i) + ';' + str(round(charge_voltage,2)) + ';' + str(round(charge_ampere,2)))
		sleep(from_config.ABTASTFREQUENZ)
		charge_voltage, charge_ampere = pRequest.charge_request()
		i +=1

except KeyboardInterrupt:
	datafile.close()
Das heißt, es wird erst das template geöffnet und es sollen die Zellen B2 mit dem timestamp, (deswegen das ; ) und dann die Zelle B3 mit der Abtastfrequenz (deswegen \n\n und ; ) beschrieben werden. Dann beginnt der Messvorgang. (ich breche hier aus Testzwecken immer mit dem keaboardinterrupt ab)

Nun habe ich eigentlich zwei Probleme,

1. Das Template wird lediglich voll geschrieben aber nicht als neue Datei mit gewünschtem Dateinamen gespeichert. Hierfür hatte ich recherchiert und eine Lösung gefunden (verstanden), an deren Umsetzung ich aber scheitere: "# once create the destination CSV file with temporary name (e.g. "temp.csv") by a FeatureWriter, and then rename it to the timestamp. i.e. move the file finally. Also letztlich eine temporäre datei erstellen und diese dann nach schluss umbennen. Ich könnte letztlich mein durchaus mikriges tamplate auch einfach innerhalb des Codes erstellen lassen, fände es aber eleganter eine template datei zu befüllen, da ich dieses gerne gegebenenfalls erweitern möchte.

2. Anstatt bei Zelle A1 anzufangen zu zählen und zu befüllen, wird immer zuerst die Zelle B6 befüllt (also wird anscheinend bei Zelle A6 angefangen zu zählen)... Bezüglich der Navigation innerhalb einer CSV Datei hatte ich herausgefunden, dass "# Columns are separated with commas, and rows are separated by line breaks, or the “\n” character.

Hier ein Beispiel, wie die logging_template.csv datei dann aussieht:

[URL=https://www.bilder-upload.eu/bild-6999b ... 2.jpg.html]

Danke für eure Anregungen :)
Sirius3
User
Beiträge: 18260
Registriert: Sonntag 21. Oktober 2012, 17:20

@Minzent: Text-Dateien sind keine Tabellen, die man beliebig füllen könnte. Du mußt also die Datei immer komplett schreiben. Wenn Du ein Template benutzen willst, kannst Du natürlich eine Vorlagedatei nehmen, deren Inhalt lesen und beim Schreiben der neuen Log-Datei benutzen.
Das with-Statement schließt die Datei automatisch, ein `close` ist nicht nötig.
Variablennamen sollen aussagekräftig sein. Was soll das p bei pRequest? Funktionsnamen sollten eine Tätigkeit beschreiben, `config` ist eher der Name der Variable (statt from_config) aber nicht der Name einer Funktion.
Warum erzeugst Du nicht gleich eine Datei mit timestamp? Oder ist es wichtig, dass die Zeit das Ende und nicht den Anfang des Ladevorgangs benennt?

Statt Strings mit + zusammenzustückeln benutze Stringformatierung, das kann auch gleich Runden. Zum Schreiben von csv-Dateien gibt es aber auch das csv-Modul, das Du benutzen solltest. Statt der low-level-Funktionen aus dem time-Modul nimm die aus dem datetime-Modul.

Der `charge_request`-Aufruf ist doppelt. Statt dessen solltest Du eine for-Schleife für i benutzen (an der Stelle empfehle ich immer eine while-True-Schleife, aber da Du zusätzlich eine Zählvariable willst, brauchst Du eine for-Schleife).
Die Abbruchbedingung `charge_voltage >= 40` wird dann am Ende der Schleife geprüft, wo sie logischerweise auch hingehört.
Benutzeravatar
__blackjack__
User
Beiträge: 14019
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Minzent: Was soll eigentlich das „Mom“ in der CSV-Datei bedeuten?
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Minzent
User
Beiträge: 16
Registriert: Dienstag 11. September 2018, 15:09

Sirius3 hat geschrieben: Mittwoch 10. Juli 2019, 15:44 Du mußt also die Datei immer komplett schreiben.
verstanden, wird umgesetzt.
Sirius3 hat geschrieben: Mittwoch 10. Juli 2019, 15:44 Variablennamen sollen aussagekräftig sein. Was soll das p bei pRequest? Funktionsnamen sollten eine Tätigkeit beschreiben, `config` ist eher der Name der Variable (statt from_config) aber nicht der Name einer Funktion.
p steht für pointer, das hat mir mal ein Kommilitone erzählt, dass das auch gängig wäre. D.h du würdest so etwas vorschlagen wie: get_values (weil die Request-Klasse enthält quasi die Methoden charge_request und discharge_request, die jeweils zwei Werte, nämlich die Spannung und die Stromstärke ausgeben). In der Config-Klasse stecken eben so Informationen wie die Abtastfrequenz, die ich darin eben ändern kann. deswegen nenne ich die variable dann from_config.ABTASTFREQUENZ in dem Beispiel.
Sirius3 hat geschrieben: Mittwoch 10. Juli 2019, 15:44 Warum erzeugst Du nicht gleich eine Datei mit timestamp? Oder ist es wichtig, dass die Zeit das Ende und nicht den Anfang des Ladevorgangs benennt?
Ich hatte es versucht:

Code: Alles auswählen

with open(time.asctime(time.localtime()),'a') as datafile:
spuckt mir dann aber eben keine csv-Datei aus. Ich wusste nicht, wie ich die Datei eindeutig als CSV definieren kann, denn time.asctime(time.localtime()).csv funktioniert nicht.
Sirius3 hat geschrieben: Mittwoch 10. Juli 2019, 15:44 Der `charge_request`-Aufruf ist doppelt. Statt dessen solltest Du eine for-Schleife für i benutzen (an der Stelle empfehle ich immer eine while-True-Schleife, aber da Du zusätzlich eine Zählvariable willst, brauchst Du eine for-Schleife).
Die Abbruchbedingung `charge_voltage >= 40` wird dann am Ende der Schleife geprüft, wo sie logischerweise auch hingehört.
Ich hab dieses Skript wie gesagt nur hier zu beispielzwecken zusammengeschustert. In meinem richtigen Programm macht das Sinn, dass der Aufruf doppelt passiert, da am Anfang des Vorgangs erstmal überprüft wird, ob eine Batterie angeschlossen ist (charge_voltage < 1?), und falls ja, dann muss im Abstand der Abtastfrequenz immer wieder die Spannung abgefragt werden, bis die Ladeschlussspannung (hier im Beispiel = 40) erreicht wird. das i habe ich nur für die CSV-Datei eingefügt und soll die Messpunktnummer darstellen. D.h bei i=0 soll mir die Batteriespannung vor Beginn des Ladevorgangs gezeigt werden, und dann eben ab i=1 die Spannung nach der ersten Abtastfrequenz etc.
Minzent
User
Beiträge: 16
Registriert: Dienstag 11. September 2018, 15:09

__blackjack__ hat geschrieben: Mittwoch 10. Juli 2019, 16:05 @Minzent: Was soll eigentlich das „Mom“ in der CSV-Datei bedeuten?
Momentane Spannung
Momentane Stromstärke
Benutzeravatar
__blackjack__
User
Beiträge: 14019
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Minzent: Ist ein bisschen komisch `Momentane Voltage` zu schreiben. Das `Mom` ist dann ein deutschsprachiger Teil unter sonst ausschliesslich englischsprachigen Bezeichnern in der Datei.

Soso, Pointer. Was ist das denn? Also in Python‽ Und warum bei `pRequest` und nicht bei allen anderen Namen auch? Und welchen Vorteil habe ich als Leser des Quelltextes von diesem `p`?

`get_values` wofür? Anstelle von `pRequest`? Nein, denn `get_values` wäre der name einer Funktion. Das ”Ding” würde wohl eher `requester` heissen und die Klasse `Requester`, denn für *einen* „request“, also eine Anfrage, steht das Objekt ja anscheinend nicht. Und in den Methodennamen müsste das `_request` dann nicht auch noch einmal stehen. Vielleicht sollte es auch eher `Battery` heissen und die Methoden `get_charge()` und `get_discharge(). Wobei ich gerade ein bisschen Probleme habe mir die beiden Methoden vorzustellen. Was machen die denn anders/was ist denn am Ergebnis anders zwischen diesen beiden Methoden.

Klassen werden in MixedCase benannt, also `Config` wenn das eine Klasse ist. Dann muss man auch nicht so komische Sachen machen wie das Exemplar davon dann `from_config` zu nennen – das heisst dann `config`. Wobei ich gerade nicht wissen möchte wie die Klasse aussieht. Was macht denn bitte die `__init__()` von dieser Klasse? Oder ist das etwa einfach nur eine Klasse als Namensraum für Konstanten auf Klassenebene? Das macht man eigentlich nicht — wir sind hier ja nicht bei Java. Und wenn, dann macht es auch keinen Sinn Exemplare davon zu erstellen, denn man kann da ja bereits über die Klasse drauf zugreifen.

`time.asctime()` gibt eine Zeichenkette zurück, und die hat natürlich kein `csv`-Attribut. Und das würde auch nicht auf magische Weise dazu führen, dass die Zeichenkette durch den Zugriff durch die Zeichenkette '.cvs' ergänzt würde, falls es ein solches Attribut gäbe. Du musst den Dateinamen schon aus den entsprechenden Daten erzeugen, also beispielsweise in dem ein `datetime.datetime`-Objekt in eine passende Vorlage mittels `format()`-Methode hineinformatiert wird, oder ab Python 3.6 auch gerne mittels f-Zeichenkettenliteral.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Minzent
User
Beiträge: 16
Registriert: Dienstag 11. September 2018, 15:09

__blackjack__ hat geschrieben: Mittwoch 10. Juli 2019, 17:04 @Minzent: Ist ein bisschen komisch `Momentane Voltage` zu schreiben. Das `Mom` ist dann ein deutschsprachiger Teil unter sonst ausschliesslich englischsprachigen Bezeichnern in der Datei.
Momentary Voltage? Sehe ich jetzt kein Problem drin. Lass uns doch bitte auf die wesentlichen Fragen konzentrieren :roll:

__blackjack__ hat geschrieben: Mittwoch 10. Juli 2019, 17:04 `get_values` wofür? Anstelle von `pRequest`? Nein, denn `get_values` wäre der name einer Funktion. Das ”Ding” würde wohl eher `requester` heissen und die Klasse `Requester`, denn für *einen* „request“, also eine Anfrage, steht das Objekt ja anscheinend nicht. Und in den Methodennamen müsste das `_request` dann nicht auch noch einmal stehen. Vielleicht sollte es auch eher `Battery` heissen und die Methoden `get_charge()` und `get_discharge(). Wobei ich gerade ein bisschen Probleme habe mir die beiden Methoden vorzustellen. Was machen die denn anders/was ist denn am Ergebnis anders zwischen diesen beiden Methoden.
Ich habe zwei Raspberrys, mit jeweils einer Meßerweiterung. Ein "MeasurementPi Charge" für den Ladevorgang und ein "MeasurementPi Discharge" für Entladevorgänge. Beide schicken die Messdaten per HTTP an einen SteuerungsPi. Deswegen zwei request-methoden, eine für die Messwerte vom LadePi und eine für die des EntladePi.
__blackjack__ hat geschrieben: Mittwoch 10. Juli 2019, 17:04 Was macht denn bitte die `__init__()` von dieser Klasse? Oder ist das etwa einfach nur eine Klasse als Namensraum für Konstanten auf Klassenebene?


exakt. Das Einlesen von .txt-Datein um eine config-Text- Datei zu machen habe ich noch nicht gelernt. deswegen erstmal als Klasse erstellt.
__blackjack__ hat geschrieben: Mittwoch 10. Juli 2019, 17:04 Und wenn, dann macht es auch keinen Sinn Exemplare davon zu erstellen, denn man kann da ja bereits über die Klasse drauf zugreifen.
Was meinst du damit? Also in meinem Beispiel: Wie kann ich den Wert ABTASTFREQUENZ aus der config-Klasse anders bekommen als über mein from_config.ABTASTFREQUENZ (ja ich weiß, deutsches Wort, wird bearbeitet. Davon abgesehen finde ich die Bezeichning mit from_config eigentlich für den Leser einleuchtend)
__blackjack__ hat geschrieben: Mittwoch 10. Juli 2019, 17:04 `time.asctime()` gibt eine Zeichenkette zurück, und die hat natürlich kein `csv`-Attribut. Und das würde auch nicht auf magische Weise dazu führen, dass die Zeichenkette durch den Zugriff durch die Zeichenkette '.cvs' ergänzt würde, falls es ein solches Attribut gäbe. Du musst den Dateinamen schon aus den entsprechenden Daten erzeugen, also beispielsweise in dem ein `datetime.datetime`-Objekt in eine passende Vorlage mittels `format()`-Methode hineinformatiert wird, oder ab Python 3.6 auch gerne mittels f-Zeichenkettenliteral.
Danke, werde ich versuchen!
OozeWithUzi
User
Beiträge: 9
Registriert: Montag 8. Juli 2019, 13:40

Minzent hat geschrieben: Donnerstag 11. Juli 2019, 09:05
__blackjack__ hat geschrieben: Mittwoch 10. Juli 2019, 17:04 Und wenn, dann macht es auch keinen Sinn Exemplare davon zu erstellen, denn man kann da ja bereits über die Klasse drauf zugreifen.
Was meinst du damit? Also in meinem Beispiel: Wie kann ich den Wert ABTASTFREQUENZ aus der config-Klasse anders bekommen als über mein from_config.ABTASTFREQUENZ (ja ich weiß, deutsches Wort, wird bearbeitet. Davon abgesehen finde ich die Bezeichning mit from_config eigentlich für den Leser einleuchtend)
Ich würde hier vermutlich einfach ein Dictionary anlegen, das dann die komplette Config beinhaltet. Denn sie eh hart im Code steht, macht ne Klasse da eher wenig Sinn.
Sirius3
User
Beiträge: 18260
Registriert: Sonntag 21. Oktober 2012, 17:20

wenn Du schon Konfigurationsdateien in Python schreibst, dann ist ein Modul das richtige und nicht eine Klasse.
Und auch dann heißt es einfach nur `config`:

Code: Alles auswählen

import config
print(config.ABTASTFREQUENZ)
Benutzeravatar
__blackjack__
User
Beiträge: 14019
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Minzent: Ich würde sagen „momentary voltage“ ist falsch weil das keinen Zeitpunkt beschreibt sondern eine kurze Zeitspanne. Und ich habe das noch nie irgendwo so gelesen oder gehört. Das war immer nur „voltage“ (oder was eben gemessen wird) oder „current voltage“. Wobei bei Stromstärke dann auch „current current“ als Bezeichnung vorkommen könnte. Ich würde es aber ganz weg lassen, denn aus der Nummer der Messung und der Abtastfrequenz wird ja eigentlich schon klar, dass es sich um die Spannung zu einem Messzeitpunkt handelt.

Da ich das wie gesagt, so noch nicht gesehen habe, könnte die Abkürzung auch dazu führen, dass sich der Leser fragt wessen Mutter da unter Strom steht. ;-)

`from_config` ist für mich nicht einleuchtend. Ich habe noch nie gesehen das *so* ein Objekt genannt wird das die Konfiguration enthält. Der Leser will doch nicht wissen wo ein Objekt seine Werte her hat, sondern was die Werte bedeuten die in dem Objekt stecken. Der übliche Name für so ein Objekt ist `config`. `from_*` ist ein Präfix der üblich ist für Funktionen oder (Klassen-)Methoden die ein Objekt aus einem anderen Objekt erstellen. Als Gegenstück zu ebenfalls üblichen `to_*`-Methoden, die ein Objekt in eine andere Repräsentation umwandeln. Zum Beispiel `pandas.DataFrame` mit `from_csv()`, `from_dict()`, und `to_csv()`, `to_dict()`, und so weiter.

Wenn das Konstanten auf Klassenebene sind, kann man aber auch einfach über die Klasse auf die Werte zugreifen ohne ein Exemplar davon zu erstellen, was ja weder Daten noch irgendwelche Funktionalität enthält. Falls die Klasse also `Config` heisst, dann einfach `Config.ABTASTFREQUENZ`. Wobei wie gesagt Klassen nur als Namensraum für Konstanten IMHO ein Missbrauch von Klassen ist. Das macht man in Java, weil man es da so machen *muss*, aber Python hat Module als Namensraum und ein `config`-Modul wie von Sirius3 gezeigt ist gängige Praxis in Python.

Wenn man kein zusätzliches Modul für die Konfiguration haben möchte, kann man die wie von OozeWithUzi vorgeschlagen auch als Wörterbuch im Modul anlegen. Mit dem externen `addict`-Modul kann man die Attributzugriffssyntax beibehalten. Ansonsten kann man dafür statt eines Wörterbuchs auch `types.SimpleNamespace` verwenden.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Minzent
User
Beiträge: 16
Registriert: Dienstag 11. September 2018, 15:09

Sirius3 hat geschrieben: Donnerstag 11. Juli 2019, 09:33 wenn Du schon Konfigurationsdateien in Python schreibst, dann ist ein Modul das richtige und nicht eine Klasse.
Und auch dann heißt es einfach nur `config`:

Code: Alles auswählen

import config
print(config.ABTASTFREQUENZ)
wird umgesetzt. Danke!
__blackjack__ hat geschrieben: Donnerstag 11. Juli 2019, 10:36 Da ich das wie gesagt, so noch nicht gesehen habe, könnte die Abkürzung auch dazu führen, dass sich der Leser fragt wessen Mutter da unter Strom steht.
Werde es (für dich) ändern ;)

Zu meinen jetzigen Änderungen und dem letzten Problem:

Code: Alles auswählen

from request import Request
from datetime import datetime
import config
from time import sleep


requester = Request()

charge_voltage, charge_ampere = requester.charge_request()


timestamp_title = time.strftime('%d-%m-%Y %H:%M:%S.csv')
timestamp_start = time.strftime('%d-%m-%Y %H:%M:%S')


try:

	with open(timestamp_title,'w') as datafile:
		line = 'Start Time;Sampling Frequency [s];Measuring point;Voltage [V];Amparage [A];End Time'
		print(line,file=datafile)
		datafile.write(timestamp_start + ';' + str(config.ABTASTFREQUENZ))
	i=0
	while charge_voltage < 40:
		with open(timestamp_title,'a') as datafile:
			datafile.write(';' + str(i) + ';' + str(round(charge_voltage,2)) + ';' + str(round(charge_ampere,2)) + '\n;')
		sleep(config.ABTASTFREQUENZ)
		charge_voltage, charge_ampere = requester.charge_request()
		i +=1

except KeyboardInterrupt:
	timestamp_end = time.strftime('%d-%m-%Y %H:%M:%S')
	with open(timestamp_title,'a') as datafile:
		datafile.write(';;;' + timestamp_end)
wie gesagt: der Code ist nur für die Frage hier zusammengebaut, dass der keaboardinterrupt hier die einzige abbruchfunktion ist macht natürlich keinen sinn, sondern dient dem testzweck. Es funktioniert jetzt soweit alles: es wird jedes mal eine neue CSV Datei erstellt, die den Timestamp der Form "11-07-2019 12:04:33.csv" hat. Beschrieben wird sie soweit auch richtig bis auf folgendes Problem: Mit dem Argument 'a', also append, beginnt man quasi in der Spalte A, aber in der ersten komplett freien Zeile, d.h, dass mein

Code: Alles auswählen

except KeyboardInterrupt:
	timestamp_end = time.strftime('%d-%m-%Y %H:%M:%S')
	with open(timestamp_title,'a') as datafile:
		datafile.write(';;;' + timestamp_end)
den Endzeitpunkt nicht in Zeile 1, Spalte F schreibt, sondern eben in Zeile x+1...
Hier ein Beispielfoto:
Bild
OozeWithUzi
User
Beiträge: 9
Registriert: Montag 8. Juli 2019, 13:40

Du könntest dir die abgetasteten Werte zwischenspeichern und dann im Interrupt alles auf einmal in die Datei schreiben.
Benutzeravatar
__blackjack__
User
Beiträge: 14019
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Minzent: Man kann halt nicht nachträglich etwas in Zeilen schreiben die bereits geschrieben wurden. Das Format ist für eine CSV-Datei aber auch sonst etwas unkonventionell, denn die beschreiben in aller Regel eine gleichmässige 2D-Tabelle, denn das ist das was die allermeisten Werkzeuge erwarten mit denen man CSV-Dateien (sinnvoll) weiterverarbeiten kann. Also in diesem Fall kein 'Start Time', 'End Time', und 'Sampling Frequency' und statt 'Measuring Point' was im Grunde ja redundante Information ist, den Zeitstempel der Messung in der Zeile. Start- und Endzeit und auch die Abtastfrequenz kann man aus diesen Zeitstempeln ermitteln. Start ist der erste Eintrag, Ende der Letzte, und die Abtastfrequenz die Differenz zwischen den beiden durch die Anzahl der Datensätze.

Alternativ könnte man die drei Werte mit den Metadaten in den ersten drei CSV-Zeilen unterbringen, oder vielleicht auch nur in einer, und vielleicht als Kommentar. Damit kämen dann weniger Werkzeuge klar, aber es gäbe da immerhin noch einige.

Ansonsten möchtest Du vielleicht keine CSV-Datei erstellen, sondern eine Exceltabelle, oder eine HDF5-Datei, oder eine SQLite-Datei, oder irgend etwas in der Richtung.
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es gibt ueberhaupt keinen Grund permanent die Datei zu oeffnen und zu schliessen. Ein einziges with um *alles* herum in dem die Datei einmal zum schreiben geoeffnet wird reicht. Und dann kannst du da auch einfach das csv-Modul benutzen.

Code: Alles auswählen

import csv
import time
from itertools import count

def gather_data(writer):
    for i in count():
        writer.writerow([i, i**3])
        time.sleep(.5)


def write_epilog(writer):
    writer.writerow([None, "result"])


def main():
    with open("/tmp/test.csv", "w") as outf:
        writer = csv.writer(outf)
        try:
            gather_data(writer)
        except KeyboardInterrupt:
            write_epilog(writer)


if __name__ == '__main__':
    main()
Antworten