ist das guter stil ?

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.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

moinsen,
ich möchte gerne eine csv-datei auslesen, um sie später in eine sqlite3 db zu übertragen/schreiben. dafür habe ich folgenden code geschrieben. ist das guter stil, ist die wahl des speicherobjektes gut usw. ?
ich würde mich sehr über eine antwort freuen.

Code: Alles auswählen

#!/usr/bin/env python3

import os, sys, sqlite3, csv

#csv-datei
file_name = "edelmetallpreise"
csv_name = f"{file_name}.csv"
csv_path = f"E:\\APPDATA\\Atom\\Python\\Data\\{csv_name}"

# Funktion zum Auslesen der csv-Datei
def readCsvFile(file_path):
    datencontainer = []
    with open(file_path) as csvdatei:
        csv_reader_object = csv.reader(csvdatei)
        for row in csv_reader_object:
            datencontainer += [row]
    return liste

def main():
    liste = readCsvFile(csv_path)
    print(liste)

if __name__ == '__main__':
    main()
vielen dank im voraus
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Sirius3
User
Beiträge: 17761
Registriert: Sonntag 21. Oktober 2012, 17:20

os, sys und sqlite3 wird importiert, aber nicht genutzt. Konstanten schreibt man KOMPLETT GROSS. Funktionen dagegen klein_mit_unterstrich. file_name ist kein Dateiname sondern nur ein Teil davon. Man erzeugt keine Listen mit einzelnen Elementen, um sie dann zu einer Liste zu addieren, sondern benutzt append. In Python ist alles ein Objekt, das in den Variablennamen zu schreiben, bringt keinen Mehrwert. `liste` ist undefiniert.
Der Kommentar vor der Funktion sollte wohl ein doc-String werden.

Code: Alles auswählen

#!/usr/bin/env python3

import csv
CSV_PATH = "E:\\APPDATA\\Atom\\Python\\Data\\edelmetallpreise.csv"

def read_csv_file(file_path):
    """Funktion zum Auslesen der csv-Datei"""
    with open(file_path) as csvdatei:
        reader = csv.reader(csvdatei)
        return list(reader)

def main():
    liste = read_csv_file(CSV_PATH)
    print(liste)

if __name__ == '__main__':
    main()
Enthält die csv-Datei keinen Header? Das sollte sie, damit man auch weiß, was welche Spalte bedeutet. Dann ist ein DictReader die bessere Wahl.
Zuletzt geändert von Sirius3 am Donnerstag 7. Mai 2020, 06:40, insgesamt 3-mal geändert.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

@Sirius3: wozu der f-String?
Sirius3
User
Beiträge: 17761
Registriert: Sonntag 21. Oktober 2012, 17:20

Übersehen und korrigiert.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@Sirius3
vielen dank. da ich "später" geschrieben habe, sind die (zukünftigen) importe mit drinnen. diese werden später noch benutzt werden. ich werde diesen thread auch weiterhin (für code-snipsel oder fragen) benutzen wollen, wenn das io ist.

so long
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Sirius3
User
Beiträge: 17761
Registriert: Sonntag 21. Oktober 2012, 17:20

sys und os sind sehr low-level-Module, werden hoffentlich auch nicht später benutzt. Und wenn sqlite3 später gebraucht wird, kann man es immer noch importieren.
Benutzeravatar
__blackjack__
User
Beiträge: 13123
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Und wie immer noch der dringende Hinweis zu den beiden Fallstricken bei CSV-Dateien: Das sind Textdateien, also sollte man beim öffnen die Kodierung angeben. Und man muss die Datei mit dem Argument ``newline=""`` öffnen, sonst erzeugt man mindestens unter Windows kaputte CSV-Dateien.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

nach

Code: Alles auswählen

print(liste) 
bekomme ich jetzt ja eine liste mit listen (eine liste pro zeile) angezeigt in dem format

Code: Alles auswählen

[['A1', 'A2', 'A3'], ['B1', 'B2', 'B3'], ... ,['N1','N2','N3']].
ansprechen kann ich diese mit (A1,... A3 sind die überschriften der tabelle):

Code: Alles auswählen

n=1
for row in liste :
    print(row[n]["A1"], row[n]["A2"], row[n]["A3"]))
    n += 1
geht das eleganter bzw. wenn ich schon DictReader verwende, würde ich gerne auch ein dict bekommen (anstelle einer liste), also im format

Code: Alles auswählen

{<key> : <value>, ... , <key> : <value>}
besten dank im voraus.
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

@__blackjack__:
danke ! sehr guter hinweis !
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Sirius3
User
Beiträge: 17761
Registriert: Sonntag 21. Oktober 2012, 17:20

Du bekommst eine Liste mit Dicts. Das ist das, was Du haben möchtest.
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

tja, wenn man seinen code nicht aktualisiert, dann bekommt man listen :oops: anstelle von dicts (na, wenn das [schon wieder] so anfängt) ...
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

ich nehme mal an, dass jeweils der letzte eintrag einer reihe in der csv-datei einem zeilenumbruch hat. wie bekomme ich den weg ?
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
__blackjack__
User
Beiträge: 13123
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Perlchamp: Du verwendest doch das `csv`-Modul, hast also mit den Teilen des Dateiformats die mit der Struktur zu tun haben, nichts mehr zu schaffen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

Code: Alles auswählen

#!/usr/bin/env python3

import sqlite3, csv
from babel.numbers import parse_decimal

# datenbank
TABLE_NAME = "edelmetallpreise"
DB_PATH = f"E:\\APPDATA\\Atom\\Python\\Data\\{TABLE_NAME}.db"

#csv-datei
CSV_PATH = f"E:\\APPDATA\\Atom\\Python\\Data\\{TABLE_NAME}.csv"

def read_csv_file(file_path):
    """ Funktion zum Auslesen einer csv-Datei """
    with open(file_path) as csvdatei:
        dict_reader = csv.DictReader(csvdatei)
        return list(dict_reader)

def csvdata_to_db(db_path,table_name,read_data):
    """ Funktion zum Eintragen der Werte aus einer csv-Datei in eine DB:
        1. Verbindung herstellen und ggf. Datenbank(datei) erzeugen.
        2. Tabelle mit Spaltennamen erstellen, falls diese noch nicht existiert.
        3. Werte von der csv-Datei in die DB eintragen
        4. Verbindung schliessen """

    connection = sqlite3.connect(db_path)
    cursor = connection.cursor()

    sql = f"CREATE TABLE IF NOT EXISTS {table_name} (gold FLOAT(20), palladium FLOAT(20), datum DATE(20));"
    cursor.execute(sql)

    for dataset in read_data:
        sql = f"INSERT INTO {table_name} VALUES(?,?,?),( \
        parse_decimal({dataset['gold']},locale='de'),\
        parse_decimal({dataset['palladium']},locale='de'),{dataset['datum']});"
        cursor.execute(sql)
    connection.commit()

    connection.close()
    print (f"Die DB {table_name}.db wurde mit der Tabelle {table_name} samt Daten erfolgreich angelegt.")


def main():
    csvdata = read_csv_file(CSV_PATH)
    csvdata_to_db(DB_PATH,TABLE_NAME,csvdata)


if __name__ == '__main__':
    main()

ich bekomme folgenden fehler angezeigt :
Traceback (most recent call last):
File "test.py", line 50, in <module>
main()
File "test.py", line 46, in main
csvdata_to_db(DB_PATH,TABLE_NAME,csvdata)
File "test.py", line 37, in csvdata_to_db
cursor.execute(sql)
sqlite3.OperationalError: near ".2019": syntax error
die csv ist folgendermaßen aufgebaut:

Code: Alles auswählen

gold,palladium,datum
1560.69,1684.28,10.11.2019
1560.69,1684.28,11.11.2019
1560.69,1684.28,12.11.2019
1560.69,1684.28,13.11.2019
2. frage:
kann ich das irgendwie intelligent lösen, dass die erste zeile der csv-datei auch verwendet wird in den sql-statements ? also hier :

Code: Alles auswählen

sql = f"CREATE TABLE IF NOT EXISTS {table_name} (gold FLOAT(20), palladium FLOAT(20), datum DATE(20));"
sql = f"INSERT INTO {table_name} VALUES(?,?,?),( \
        parse_decimal({dataset['gold']},locale='de'),\
        parse_decimal({dataset['palladium']},locale='de'),{dataset['datum']});"
und klar: wie kann man's smarter machen ...

edit: semikolons zum abschließen der sql-statements gesetzt. trotzdem besteht der fehler noch.

vielen dank im voraus.
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
__blackjack__
User
Beiträge: 13123
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Perlchamp: In die SQL-Anweisung gehören Platzhalter für Werte. Die hast Du auch und dann noch die Werte selber, wobei das auch noch falsch, denn `parse_decimal()` ist beispielsweise Teil des ”SQL” das Du da erzeugst und nicht Teil des Python-Programms. Alles nach "VALUES(?,?,?)" also auch schon das Komma gehören nicht in die Zeichenkette mit dem SQL. Die Werte werden als Sequenz (Liste, Tupel, …) als zweites Argument an `execute()` übergeben. Man bastelt keine Werte per Zeichenkettenformatierung in SQL-Anweisungen. Potentiell gefährlich und ineffizient.

Was soll die "(20)" bei FLOAT und DATE? Das fliegt Dir nur deswegen nicht um die Ohren weil Sqlite3 sehr locker mit Typen umgeht. Man kann das Datum auch als Zeichenkette übergeben und hoffen. Richtig wäre `datetime.date`-Objekte zu verwenden. Denn das DD.MM.YYY-Format wird zwar so als Zeichenkette in der DB gespeichert, aber Abfragen zum Beispiel über alles von Datum A bis B oder alles grösser Datum C wird damit dann nicht mehr funktionieren.

In der Tabelle fehlt ein Primärschlüssel und ein UNIQUE-Constraint für das Datum, sofern das nicht der Primärschlüssel ist. Ich würde aber einen künstlichen anlegen und das Datum UNIQUE machen.

Ist auch die Frage ob Gold und Palladium tatsächlich Spalten sind, oder nicht doch eher Werte in einer "sorte"-Spalte die einen Fremdschlüssel in eine Tabelle mit den Metallen enthält. Dann wären die Spalten Sorte und Datum gemeinsam UNIQUE.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

vielen dank !
In der Tabelle fehlt ein Primärschlüssel und ein UNIQUE-Constraint für das Datum, sofern das nicht der Primärschlüssel ist. Ich würde aber einen künstlichen anlegen und das Datum UNIQUE machen.
Ist auch die Frage ob Gold und Palladium tatsächlich Spalten sind, oder nicht doch eher Werte in einer "sorte"-Spalte die einen Fremdschlüssel in eine Tabelle mit den Metallen enthält. Dann wären die Spalten Sorte und Datum gemeinsam UNIQUE.
... bahnhof. aber ich mach mich mal schlau ...

so long
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

Code: Alles auswählen

#!/usr/bin/env python3

import sqlite3, csv
from babel.numbers import parse_decimal

# datenbank
TABLE_NAME = "edelmetallpreise"
DB_PATH = f"E:\\APPDATA\\Atom\\Python\\Data\\{TABLE_NAME}.db"

#csv-datei
CSV_PATH = f"E:\\APPDATA\\Atom\\Python\\Data\\{TABLE_NAME}.csv"

def read_csv_file(file_path):
    """ Funktion zum Auslesen einer csv-Datei """
    with open(file_path) as csvdatei:
        dict_reader = csv.DictReader(csvdatei)
        return list(dict_reader)

def csvdata_to_db(db_path,table_name,read_data):
    """ Funktion zum Eintragen der Werte aus einer csv-Datei in eine DB:
        1. Verbindung herstellen und ggf. Datenbank(datei) erzeugen.
        2. Tabelle mit Spaltennamen erstellen, falls diese noch nicht existiert.
        3. Werte von der csv-Datei in die DB eintragen
        4. Verbindung schliessen """

    connection = sqlite3.connect(db_path)
    cursor = connection.cursor()

    sql = f"CREATE TABLE IF NOT EXISTS {table_name} (gold FLOAT, palladium FLOAT, datum DATETIME);"
    cursor.execute(sql)

    for dataset in read_data:
        sql = f"INSERT INTO {table_name} VALUES(?,?,?);"
        cursor.execute(sql, (dataset['gold'],dataset['palladium'],dataset['datum']))
        connection.commit()

    connection.close()
    print (f"Die DB {table_name}.db wurde mit der Tabelle {table_name} samt Daten erfolgreich angelegt.")


def main():
    csvdata = read_csv_file(CSV_PATH)
    csvdata_to_db(DB_PATH,TABLE_NAME,csvdata)

if __name__ == '__main__':
    main()
so klappt es, keine fehlermeldung :-)
Die DB edelmetallpreise.db wurde mit der Tabelle edelmetallpreise samt Daten erfolgreich angelegt.

Code: Alles auswählen

#!/usr/bin/env python3

import sqlite3, csv

# datenbank
TABLE_NAME = "edelmetallpreise"
DB_PATH = f"E:\\APPDATA\\Atom\\Python\\Data\\{TABLE_NAME}.db"

#csv-datei
CSV_PATH = f"E:\\APPDATA\\Atom\\Python\\Data\\{TABLE_NAME}.csv"

def data_from_db(db_path,table_name):
    """Funktion zum Auslesen der Daten aus der DB:
    1. Verbindung herstellen
    2. Werte aus der DB-Tabelle auslesen
    3. Verbindung schliessen"""
    connection = sqlite3.connect(db_path)
    cursor = connection.cursor()

    sql = f"SELECT * FROM {table_name};"
    cursor.execute(sql)
    content = cursor.fetchall()
    return content

    connection.close()

def main():
    content = data_from_db(DB_PATH,TABLE_NAME)
    print(content)

if __name__ == '__main__':
    main()
klappt auch, auch wenn ich separat/explizit nach gold, palladium oder datum auslese. :-)
mit unique und co werde ich mich jetzt auseindersetzen. gibt's jetzt noch schönheitsreparaturen, die man vollziehen kann ?

so long
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Benutzeravatar
__blackjack__
User
Beiträge: 13123
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Perlchamp: So klappt es nicht, auch wenn keine Fehlermeldung kommt. Jetzt hast Du ja noch nicht einmal mehr die Zahlen tatsächlich in Zahlen umgewandelt. Und das Datum ist weiterhin falsch. Bei DATETIME wäre der passende Datentyp `datetime.datetime`. Wobei man an der Stelle TIMESTAMP als SQL-Typ nehmen sollte, denn dann kann `sqlite3` auch beim Auslesen die Umwandlung automatisch machen.

Ich persönlich verwende ja auch fast immer SQLAlchemy statt das jeweilige DB-Modul direkt. Bei SQLite bekommt man da dann beispielsweise auch die Umwandlung von DATETIME beim auslesen, oder kann DATE verwenden, denn zumindest laut Beispieldaten brauchst Du den Zeitanteil ja gar nicht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Perlchamp
User
Beiträge: 172
Registriert: Samstag 15. April 2017, 17:58

Code: Alles auswählen

#!/usr/bin/env python3

import sqlite3, csv
from babel.numbers import parse_decimal
from datetime import datetime
from dateutil.parser import parse

# datenbank
TABLE_NAME = "edelmetallpreise"
DB_PATH = f"E:\\APPDATA\\Atom\\Python\\Data\\{TABLE_NAME}.db"
CONNECTION = None

#csv-datei
CSV_PATH = f"E:\\APPDATA\\Atom\\Python\\Data\\{TABLE_NAME}.csv"

def read_csv_file(file_path):
    """ Funktion zum Auslesen einer csv-Datei
        zurückgegeben wird ein Dictionary """
    with open(file_path) as csvdatei:
        dict_reader = csv.DictReader(csvdatei)
        return list(dict_reader)

def csvdata_to_db(db_path,table_name,read_data):
    """ Funktion zum Eintragen der Werte aus einer csv-Datei in eine DB:
        1. Verbindung herstellen
        2. gegebenenfalls Datenbank(datei) erzeugen.
        3. Tabelle mit Spaltennamen erstellen, falls diese noch nicht existiert.
        4. Daten vor dem Eintragen in die DB aufbereiten
        5. aufbereitete Werte der csv-Datei in die DB eintragen
        6. Verbindung schliessen """

    try:
        connection = sqlite3.connect(db_path)
    except sqlite3.Error as e:
        print(e)
    except:
        print("Laufzeitfehler im Code")
    finally:
        if (CONNECTION is not None):
            connection.close()

    cursor = connection.cursor()
    sql = f"CREATE TABLE IF NOT EXISTS {table_name} (id INT PRIMARY KEY, gold DECIMAL, palladium DECIMAL, datum DATE);"
    cursor.execute(sql)

    for dataset in read_data:
        id = int(dataset['id'])
        gpreis = parse_decimal(dataset['gold'], locale='en')
        ppreis = parse_decimal(dataset['palladium'], locale='en')
        datum = parse(dataset['datum'])
        datum = datum.date()

        sql = f"INSERT INTO {table_name} VALUES(?,?,?,?)"
        cursor.executemany(sql,(id, gpreis, ppreis, datum))
        connection.commit()
    
    connection.close()
    print (f"Die DB {table_name}.db wurde mit der Tabelle {table_name} samt Daten erfolgreich angelegt.")


def main():
    csvdata = read_csv_file(CSV_PATH)
    csvdata_to_db(DB_PATH,TABLE_NAME,csvdata)

if __name__ == '__main__':
    main()
also mein latein ist am ende, meine nerven bald auch :shock: . ich bekomme immer wieder denselben fehler angezeigt, habe im netz gesucht und nichts gefunden (vieles stammt noch aus den 90er ...). ich habe alle entries geparst (int, decimal, decimal, date[time]), habe einen primary key angelegt (spalte 'id' ist dazugekommen), aber nichts zu machen ...
denn zumindest laut Beispieldaten brauchst Du den Zeitanteil ja gar nicht.
das hier ist ja nur der anfang. das datum benötige ich weil ich später ein diagramm (chart) erstellen möchte mit den preisen des letzten halben jahres. d.h. immer wenn täglich ein neuer eintrag hinzukommt, und irgendwann die anzahl der einträge (180) überschritten wird, muss der älteste eintrag raus bzw. es müssen dann die letzten 180 einträge angezeigt werden. insofern ist es gut, dass die neuen einträge jeweils in die letzte zeile kommen (x-achse = zeitachse von alt nach neu [links nach rechts]) ...

und ja: wenn man es smarter machen kann, immer wieder sehr gerne !

im voraus besten dank, auch für eure zeit ! bleibt gesund !
so long
wer lesen kann ist klar im Vorteil ;-)
es gibt keine Probleme, sondern nur Lösungen !
Bildung ist die Freude auf mich selbst !
Sirius3
User
Beiträge: 17761
Registriert: Sonntag 21. Oktober 2012, 17:20

Eine Konstante CONNECTION, die immer None ist, ist nicht sehr sinnvoll. Vor allem das Prüfen in einer Exception, macht dann auch keinen Sinn; übrigens, die Klammern dort sind überflüssig. Apropos Fehlerbehandlung, die ist an der Stelle komplett unsinnig und kann ersatzlos gelöscht werden, die sorgt nur dafür, dass sinnvolle Meldungen unterdrückt werden und das Programm zwei Zeilen später mit einer schwer verständlichen Fehlermeldung trotzdem abstürzt.
Eine ID ist ja deshalb ein Primary Key, weil er eindeutig sein mußt und deshalb meist von der Datenbank selbst vergeben, wenn man den also händisch setzt, ist das mit der Eindeutigkeit unter umständen ein Problem. Bei INSERT auch immer die konkreten Spaltennamen angeben.
Ein executemany-Aufruf mit nur einem Element ist falsch.
Tabellennamen sollten eigentlich etwas fixes sein und nicht variabel.
Im Dokstring sollte stehen, was die Funktion tut, und nicht nochmal der Programmcode in Worten.
Dass Daten irgendwann gelesen wurden, ist nicht relevant, das "gelesen" in `read_data` bietet also keinen Mehrwert, weil die Funktion genausogut mit zufällig erzeugten Daten funktionieren würde. Der Inhalt ist relevant: `edelmetallpreise`.
Bitte keine Abkürzungen, wenn man getreidepreis meint, sollte man das auch schreiben und nicht gpreis.

Code: Alles auswählen

def csvdata_to_db(db_path, edelmetallpreise):
    """ Trägt die Daten edelmetallpreise in die Datenbank """
    connection = sqlite3.connect(db_path)
    cursor = connection.cursor()
    cursor.execute("CREATE TABLE IF NOT EXISTS edelmetallpreise (id INT PRIMARY KEY, gold DECIMAL, palladium DECIMAL, datum DATE)")
    for dataset in edelmetallpreise:
        gold_preis = parse_decimal(dataset['gold'], locale='en')
        palladium_preis = parse_decimal(dataset['palladium'], locale='en')
        datum = parse(dataset['datum'])
        cursor.execute("INSERT INTO edelmetallpreise (gold, palladium, datum) VALUES (?,?,?)",(gold_preis, palladium_preis, datum))
    connection.commit()
    connection.close()
    print (f"Die DB {db_path} wurde mit der Tabelle edelmetallpreise samt Daten erfolgreich angelegt.")
Antworten