Problem mit psycopg2

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Benutzeravatar
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

Hallo zusammen,

ich wollte mal ein wenig mit beidem, PostgreSQL und Python, herumspielen und habe mir dabei ein nonsense-Scriptchen gebaut. Dieses soll nichts weiter tun, als einen zufälligen String aus Uppercase, Lowercase, Zahlen und Punktierungszeichen zwischen 1 und 255 Zeichen Länge zu erzeugen und das ganze in eine PostgreSQL Datenbank folgender Struktur eintragen:

Code: Alles auswählen

CREATE TABLE rainbow (
    value text NOT NULL,
    md5_hash character(33) NOT NULL,
    "time" timestamp with time zone DEFAULT now() NOT NULL
);
ALTER TABLE ONLY rainbow
    ADD CONSTRAINT rainbow_pkey PRIMARY KEY (value, md5_hash);
ALTER TABLE ONLY rainbow
    ADD CONSTRAINT rainbow_value_key UNIQUE (value);
Ehe hierzu schlaue Kommentare aufgrund des Namens kommen: Nein, es soll keine Rainbow Table sein; das fiel mir nur beim "My Little Pony" gucken so ein ;D

Hierzu folgender Python Code:

Code: Alles auswählen

#!/usr/bin/env python3

import psycopg2
import random
import string
import sys


def randomword(length):
    return ''.join(random.choice(string.ascii_letters + string.digits + string.punctuation) for i in range(length))


def insertdata(num=10, max=1000):
    try:
        random.seed()
        conn = psycopg2.connect(database='rainbow1', user='rainbow1_user', password='passwort_0815', host='localhost')
        cur = conn.cursor()
        cur.execute("SET TIME ZONE 'Europe/Berlin';")
        rowcount = 0
        for i in range(num):
            val = randomword(random.randint(1, 255))
            cur.execute("SELECT * FROM rainbow WHERE value = %s;", val)
            print("Hello")
            if not i == 0 and not i % max:
                conn.commit()
    finally:
        conn.commit()
        cur.close()
        conn.close()

try:
    count = int(sys.argv[1]) - insertdata(int(sys.argv[1]))
except IndexError:
    count = 10 - insertdata()
Nun zu meiner Frage:
Ich habe in meiner IDE (PyCharm) einen Breakpoint auf die Zeile mit "print("Hello")" gesetzt und den Debugger angeworfen. Die Zeile, sowie alle folgenden scheinen übersprungen zu werden und es wird gleich in die "finally" Routine eingestiegen.
Das verstehe ich nicht so ganz ... das sollte doch nur passieren, wenn es bei der Ausführung des SELECTS in der Zeile zuvor zu einem Fehler kommt, oder? Allerdings kann ich da keinen Fehler entdecken. Der Code ist an der Stelle doch eigentlich nahezu 1:1 der ganz oben auf der Dokuseite von psycopg2: http://initd.org/psycopg/docs/usage.html :

Code: Alles auswählen

>>> import psycopg2

# Connect to an existing database
>>> conn = psycopg2.connect("dbname=test user=postgres")

# Open a cursor to perform database operations
>>> cur = conn.cursor()

# Execute a command: this creates a new table
>>> cur.execute("CREATE TABLE test (id serial PRIMARY KEY, num integer, data varchar);")

# Pass data to fill a query placeholders and let Psycopg perform
# the correct conversion (no more SQL injections!)
>>> cur.execute("INSERT INTO test (num, data) VALUES (%s, %s)",
...      (100, "abc'def"))

... usw.

Kann mir da einer helfen?

PS:
Ich habe mir mal mit einer except Anweisung den Fehler ausgeben lassen, verstehe aber auch da nicht ganz was passiert:

Code: Alles auswählen

(<class 'TypeError'>, TypeError('not all arguments converted during string formatting',), <traceback object at 0x7f8706898388>)
sebastian0202
User
Beiträge: 168
Registriert: Montag 9. Mai 2016, 09:14
Wohnort: Berlin

Ich denke, du hast da einen Denkfehler. Schaue dir mal folgenden Beispiel Code an.

Code: Alles auswählen

for i in range(0,10):
    print "Schleife Nummer: %s" % (i)
    try:
        print "Erste Zeile 1"
        break
        print "Zweite Zeile 2"
    except:
        print "Ich bin im Except Zweig"
    finally:
        print "Finally kann ich sagen, es geht"
    
Was soll denn dein PyCharme Breakpoint bewirken?


Die Parameterübergabe bei execute sieht auch nicht konform aus.
Habe das hier gefunden..
http://initd.org/psycopg/docs/usage.htm ... parameters
While the mechanism resembles regular Python strings manipulation, there are a few subtle differences you should care about when passing parameters to a query:

The Python string operator % is not used: the execute() method accepts a tuple or dictionary of values as second parameter. Never use % or + to merge values into queries.

The variables placeholder must always be a %s, even if a different placeholder (such as a %d for integers or %f for floats) may look more appropriate:

>>> cur.execute("INSERT INTO numbers VALUES (%d)", (42,)) # WRONG
>>> cur.execute("INSERT INTO numbers VALUES (%s)", (42,)) # correct

For positional variables binding, the second argument must always be a sequence, even if it contains a single variable. And remember that Python requires a comma to create a single element tuple:

>>> cur.execute("INSERT INTO foo VALUES (%s)", "bar") # WRONG
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar")) # WRONG
>>> cur.execute("INSERT INTO foo VALUES (%s)", ("bar",)) # correct
>>> cur.execute("INSERT INTO foo VALUES (%s)", ["bar"]) # correct

Only variable values should be bound via this method: it shouldn’t be used to set table or field names. For these elements, ordinary string formatting should be used before running execute().
Zuletzt geändert von sebastian0202 am Dienstag 10. Mai 2016, 10:28, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@Judge: dass da sofort in den finally-Block gesprungen wird liegt daran, dass in Zeile 22 ein TypeError geworfen wird, der sollte Dir auch angezeigt werden. Zudem sollte ein try-Block möglichst kurz sein, um wirklich nur den eigentlichen Fehler abzufangen, und nicht noch was anderes:

Code: Alles auswählen

try:
    num = int(sys.argv[1]))
except IndexError:
    num = 10
count = num - insertdata(num)
BlackJack

@Judge: Warum soll das nur an der Zeile direkt vor dem `print()` liegen können? Jede Zeile vom ``try`` bis dort hin könnte es sein. Welche es ist, und warum, sollte Dir doch aber die Ausnahme verraten, die Du bekommst. Die ”Ausnahmslose” Erklärung wäre, dass Du das Programm mit 0 als Argument aufrufst. ;-)

Die Kombination aus SELECT und `commit()` muss nicht zwingend sinnfrei sein, aber ist das wirklich so gewollt? Und dass das Ergebnis des SELECT nicht verwertet wird‽

Das `random.seed()` solltest Du da raus nehmen, das macht keinen Sinn.

Wo Du auf jeden Fall ein Problem bekommen wirst, ist der Versuch von einer Zahl `None` abzuziehen.
Benutzeravatar
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

sebastian0202 hat geschrieben:Was soll denn dein PyCharme Breakpoint bewirken?
na, eben das was ich schrieb zu zeigen: Das an der Stelle zuvor ausgestiegen wird.
Das war ein Schritt mich dem Problem anzunähern.
Sirius3 hat geschrieben:@Judge: dass da sofort in den finally-Block gesprungen wird liegt daran, dass in Zeile 22 ein TypeError geworfen wird, der sollte Dir auch angezeigt werden. Zudem sollte ein try-Block möglichst kurz sein, um wirklich nur den eigentlichen Fehler abzufangen, und nicht noch was anderes.
Da hast Du recht; das sollte ich mal zusammenstauchen. Danke für die Anregung.
Das mit dem TypeError hatte ich kurz nachdem ich den Beitrag geschrieben hatte, als "PS" nachgereicht :oops:
BlackJack hat geschrieben:@Judge: Warum soll das nur an der Zeile direkt vor dem `print()` liegen können? Jede Zeile vom ``try`` bis dort hin könnte es sein. Welche es ist, und warum, sollte Dir doch aber die Ausnahme verraten, die Du bekommst. Die ”Ausnahmslose” Erklärung wäre, dass Du das Programm mit 0 als Argument aufrufst. ;-)
Ja, korrekt. Hier fehlte die Info, das ich rund um die Zeile "cur.execute(...)" Breakpoints gesetzt habe und mich Schritt für Schritt weitergeklickt hatte. Da der print die erste Zeile war, die nicht mehr ausgeführt wurde, wusste ich, das der Fehler in dieser Zeile geschieht.
BlackJack hat geschrieben:Die Kombination aus SELECT und `commit()` muss nicht zwingend sinnfrei sein, aber ist das wirklich so gewollt? Und dass das Ergebnis des SELECT nicht verwertet wird‽
Auch hier fehlte etwas; ich habe für die Analyse des Fehlers nicht notwendigen Code aus Gründen der Übersichtlichkeit entfernt; ich schreibe in die Spalten (value, md5_hash, time) eigentlich die Werte (%s, md5(%s), now()); mit dem vorigen Select wollte ich ausprobieren wie sich PostgreSQL hier verhält (wie gesagt: Es ist Spiel- und Experimentiercode, ohne tieferen Sinn): Ich habe hier ja (bewusst) nicht ausgeschlossen, das ein zufallsgenerierter Wert für "value" mehrfach auftritt. Würde PostgreSQL nun hergehen und die Funktionsausführung für now() und md5() erst ausführen und DANN prüfen, ob der Wert von value "unique" ist, wäre hier umsonst (wenn auch wenig) Rechenzeit für die Erzeugung des MD5 Hashes aufgewendet worden. Daher wollte ich mit einem SELECT auf das Feld, erstmal prüfen ob es den Wert schon gibt, und nur, falls nicht, den anderen Query losjagen.
Mir ist bekannt wie sinnlos das ist: Bei einer so großen Tabelle, wie diese es nach kurzer Zeit wäre, ist ein SELECT über die ganze DB immer "teurer" als das bischen Rechenzeit für die MD5 Summe. Aber das könnte, je nach Funktion, ja auch mal anders sein. Und solche Fälle spiele ich mit dem Code einfach mal durch.
BlackJack hat geschrieben: Das `random.seed()` solltest Du da raus nehmen, das macht keinen Sinn.
OK, danke.
Muss random garnicht initialisiert werden, oder nur nicht so? Sprich: Soll ich das irgendwo vor der untersten try...except suite eintragen, oder einfach rausnehmen?
BlackJack hat geschrieben:Wo Du auf jeden Fall ein Problem bekommen wirst, ist der Versuch von einer Zahl `None` abzuziehen.
Auch hier fehlt einfach Code .. aber danke trotzdem, Adlerauge ;)
BlackJack

@Judge: Ich verstehe wie sebastian0202 den Sinn der Breakpoints hier nicht. Da wird eine Ausnahme ausgelöst welche die Art und die genaue Zeile des Problems liefert. Wieso sollte man sich da mit Breakpoints dieser Zeile nähern wollen? Die wird doch bereits ohne das man etwas macht, frei Haus geliefert.

Ich glaube nicht das zwei Datenbankanfragen, bei der eine potentiell eingespart werden kann, weniger Zeit verbraucht als eine potentiell unnötige MD5-Berechnung. Kommunikation mit der Datenbank, eventuell parsen von SQL und umwandeln in einen Ablaufplan, und den dann ausführen, ist alles deutlich mehr Arbeit als eine Prüfsumme von einer relativ kurzen Zeichenkette, würde ich mal behaupten.

`random.seed()` einfach rausnehmen. Das wird initialisiert wenn man das Modul importiert. Danach ist der Aufruf bestenfalls sinnfrei, schlimmstenfalls kann es zu *weniger* (Pseudo)Zufall führen. Die Funktion ist sinnvoll wenn man für Tests absichtlich wiederholbare Zufallsfolgen braucht und man sie dafür mit einem Wert aufruft.
Benutzeravatar
Judge
User
Beiträge: 129
Registriert: Mittwoch 13. Juni 2012, 22:27
Wohnort: Ratingen
Kontaktdaten:

BlackJack hat geschrieben:@Judge: Ich verstehe wie sebastian0202 den Sinn der Breakpoints hier nicht. Da wird eine Ausnahme ausgelöst welche die Art und die genaue Zeile des Problems liefert. Wieso sollte man sich da mit Breakpoints dieser Zeile nähern wollen? Die wird doch bereits ohne das man etwas macht, frei Haus geliefert.
Stimmt ja - da habt ihr beide ja auch recht. Habe ich ja dann auch so nachvollzogen.
Ich bin halt noch absoluter Neuling in Sachen Python und Programmierung im allgemeinen. Mache bisher nur bash; mag sein das ich mir den foo mit dem "Zeilenweise nachvollziehen" dabei zu sehr angewöhnt habe. Aber eben das ist ja auch Sinn so einer Spielwiese.
BlackJack hat geschrieben:Ich glaube nicht das zwei Datenbankanfragen, bei der eine potentiell eingespart werden kann, weniger Zeit verbraucht als eine potentiell unnötige MD5-Berechnung. Kommunikation mit der Datenbank, eventuell parsen von SQL und umwandeln in einen Ablaufplan, und den dann ausführen, ist alles deutlich mehr Arbeit als eine Prüfsumme von einer relativ kurzen Zeichenkette, würde ich mal behaupten.
Sage ich ja :) Ist reiner Spielkram, über den ich mich besseren Lösungen erstmal annähere.
Ich habe jetzt beide Varianten, mit dem zusätzlichen Select und ohne, je 3x mit 100.000 Datensätzen ausgeführt und mit time unter Linux die Execution Time gemittelt:

Mit dem Select sind's: 32,07 Sekunden
Ohne sind's: 18,50 Sekunden
Also ist der eine Select für eine Verlangsamung um rund 73% verantwortlich.

Für mich interessant mal zu erleben, wie groß der Einfluss einer einzigen, unnötigen DB Abfrage das beeinflusst.

Was ich zudem wollte ist, das wenn ich der Funktion insertdata() einen Wert für num übergebe, sagen wir: 100, ich auch mitbekomme, wenn der in einer Iteration nicht eingetragen wird, weil er z.B. bereits vorhanden ist. Hierzu hat man ja prinzipiell mehrere Möglichkeiten:
  1. Man kann das mit so einem vorgelegtem Select erst prüfen, das als Rückgabewert der Funktion mit ausgeben und dann halt so lange die Funktion wieder aufrufen, bis da 0 zurück kommt.
  2. Man kann versuchen nach erfolgtem Insert (ggf. mit "ON CONFLICT DO NOTHING" damit kein Fehler geschmissen wird) irgendwie psycopg2 die Info zu entlocken, was er gemacht hat und mit welchem Ergebnis (da herauszufinden, wie das geht bin ich noch bei).
  3. Auch das wieder in eine try...except suite eintragen (ohne "ON CONFLICT DO NOTHING"), und so herausbekommen, ob das erfolgreich war oder nicht.
Bin da halt noch am rumprobieren.
BlackJack hat geschrieben:`random.seed()` einfach rausnehmen. Das wird initialisiert wenn man das Modul importiert. Danach ist der Aufruf bestenfalls sinnfrei, schlimmstenfalls kann es zu *weniger* (Pseudo)Zufall führen. Die Funktion ist sinnvoll wenn man für Tests absichtlich wiederholbare Zufallsfolgen braucht und man sie dafür mit einem Wert aufruft.
Danke für die Erläuterung!

@sebastian0202:
Danke, genau das war es! Der TypeError kommt, weil psycopg2 bei der von mir gewählten Schreibweise, selbst wenn man nur einen einzigen Wert zu übergeben hat, als zweites Argument immer eine Sequenz erwartet. So funktioniert die Zeile dann auch:

Code: Alles auswählen

cur.execute("SELECT * FROM rainbow WHERE value = %s;", (val,))
Antworten