SQlite3 - Verbindungsobjekt bzw. Cursor in Funktion

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Guten Morgen!
Es war sehr sinnvoll wieder zurück zu schalten, dann ich habe (wie ich soeben feststellte) wohl u.a. ein Problem bei den Rückgabewerten einer Funktion.

Wenn ich folgendes schreibe:

Code: Alles auswählen

def db_sqlite_verbindung():
    import sqlite3
    db_name = "c:/gb_sqlite.db"
    verbindung = sqlite3.connect(db_name)
    schreibmarke = verbindung.cursor()
    return schreibmarke
Erstelle ich ein Verbindungsobjekt "verbindung" und ein cursor-Objekt "schreibmarke". Müsste ich nach Funktionsaufruf von db_sqlite_verbindung() nicht (ausserhalb der Funktion) mit der schreibmarke weiterarbeiten können?

LG
Daniel
deets

Als allererstes: gewoehn dir mal bitte sofort ab, importe lokal in Funktionen zu machen. Dafuer gibt es gelegentlich Gruende. Aber niemals fuer Anfaenger.

Zu deiner Frage: ja, und da du nicht schreibst, was *nicht* geht sehe ich auch nicht, was dazu mehr zu sagen waere.

Doch generell ist dein Vorgehen nicht korrekt, denn du verlierst die Verbindung. Und damit kannst du mit deinem Cursor nur bis zum naechsten Commit arbeiten.

Wenn du mittels Django oder SQLAlchemy und deren ORMs arbeiten wuerdest, koenntest du stattdessen darauf bauen, dass die mit Verbindungs & Transaktionsmanagement daherkommen, und dich mal auf *deinen* Code konzentrieren.

Es ist ja durchaus loeblich, Dinge verstehen zu wollen. Aber du wirkst gerade wie eine etwas aus der Bahn geratene Flipperkugel...
BlackJack

@mcdaniels: Ja das müsstest Du. Wieso? Kannst Du nicht? Allerdings nur lesend, denn ohne das Verbindungsobjekt kannst Du keine Veränderungen „commmit”en.

Ich würde es übrigens nicht `schreibmarke` nennen. Er wird eher selten dazu verwendet tatsächlich etwas an der Stelle zu schreiben oder zu verändern an der er sich gerade befindet. Die DB-API unterstützt das auch gar nicht erst.

Was Dir noch passieren kann — das habe ich jetzt nicht ausprobiert — ist das der Programmierer von `sqlite3` nicht damit gerechnet hat, dass jemand das Verbindungsobjekt weg wirft, obwohl er noch einen Cursor davon benutzen möchte, und deshalb beim Abräumen des Verbindungsobjekts die Verbindung automatisch geschlossen wird.
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo,
Aber du wirkst gerade wie eine etwas aus der Bahn geratene Flipperkugel...
:lol:
Du hast insofern recht, als dass ich mich mal hier und mal dort "aufhalte". Das ist aber nicht zielführend, wie ich vor Kurzem feststellte.
Ich will NICHT weiter mit Django arbeiten, solange ich derlei Probleme habe. Es soll ja kein offizielles Projekt werden sondern nur ein "Grundlagen pauken".

Was ich eigentlich mit dem Return (dem cursor) machen wollte war zb das hier:

Code: Alles auswählen

schreibmarke.execute(...)
Jedoch kennt Python ausserhalb der Funktion wohl schreibmarke nicht (NameError: 'schreibmarke' is not defined)

@Blackjack:
dass jemand das Verbindungsobjekt weg wirft, obwohl er noch einen Cursor davon benutzen möchte, und deshalb beim Abräumen des Verbindungsobjekts die Verbindung automatisch geschlossen wird.
D.h. ich sollte auch die das "Verbindungsobjekt" per return zurück geben?

LG
Daniel
deets

Wie du meinst. Dein Fehler ist ohne groesseren Kontext aber eh nicht zu verstehen. Dir ist aber schon klar, dass du Rueckgabewerte einem Namen zuweisen musst, damit du sie benutzen kannst? Und nochmal wiederholt: du arbeitest falsch, das zentrale Objekt einer DB ist die Verbindung. Die darfst du so nicht wegwerfen.

Code: Alles auswählen


def connect(uri):
      return sqlite.connect(uri)

def tuwas(conn):
      c  = conn.cursor()
      c.execute("some sql")
      return c


die_verbindung = connect(uri) # rueckgabewerte muessen gebunden werden

for row in tuwas(die_verbindung):
     print row
Ich habe bewusst andere Namen fuer den Rueckgabewert conn gewaehlt, damit du nicht denkst, bloss weil etwas den selben Namen hat, waere es das gleiche. In Python sind (gottlob) nicht alle Namen globale Variablen...
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Du musst den Rückgabewert einer Funktion auch an einen Namen binden!

Code: Alles auswählen

def foo(name):
    s = "{}, the mighty!".format(name)
    return s

# so geht 's natürlich nicht!
foo("Hyperion")
print(s)

# so ist es richtig:
s = foo("Hyperion")
print(s)
`s` ist nur innerhalb der Funktion verfügbar. Außerhalb muss ich einen *neuen* Namen hinschreiben, an den ich das Rückgabe-Objekt binde. In diesem Falle wähle ich auch `s` - das ist aber nicht dasselbe `s` wie innerhalb der Funktion!
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Dir ist aber schon klar, dass du Rueckgabewerte einem Namen zuweisen musst
Jetzt wieder, ja... Danke!

Das war in diesem Fall der "Fehler".

Dabei hatte ich das alles schon mal gewusst und wieder vergessen...
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo nochmals,
hätte da noch eine Frage betreffend dem Einfügen von Daten in die SQLite DB.

Ich habe eine DB mit folgenden Tables erstellt:

Code: Alles auswählen

CREATE TABLE guestbook (name text, kommentar text)
Nun wollte ich per:

Code: Alles auswählen

def eingabe():
    data = []
    name = raw_input('Name: ')
    comment = raw_input('Kommentar: ')
    data.append(name)
    data.append(comment)
    for elements in data:
        my_cursor.execute('INSERT INTO guestbook VALUES (?,?)', elements)
    my_db_connection.commit()
Daten in die DB schreiben, erhalte jedoch diesen Fehler:
my_cursor.execute('INSERT INTO guestbook VALUES (?,?)', elements)
ProgrammingError: Incorrect number of bindings supplied. The current statement uses 2, and there are 6 supplied.

D.h. bei der Eingabe (nämlich Daniel bei Name) wird jeder einzelne Buchstabe gezählt.

Nun heißt es, dass die cursor.execute() Methode als 2ten Parameter eine Sequenz erwartet. Mit dem kann ich im Moment nichts anfangen.

Was ist damit genau gemeint?

LG
Daniel
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

mcdaniels hat geschrieben: Nun heißt es, dass die cursor.execute() Methode als 2ten Parameter eine Sequenz erwartet. Mit dem kann ich im Moment nichts anfangen.
Da stolpert man in der Doku ja fast schon drüber ;-) Sequence Types

Eine Liste gehört demzufolge dazu.

Aber... wieso zum Geier iterierst Du über diese Liste? Du willst doch gerade die Liste "am Stück" als Parameter übergeben. Wie kommst Du also auf diese Idee?

Das Binden an die beiden Namen ist auch unnötig, wenn Du diese einfach nur in einen Container packst:

Code: Alles auswählen

    name = raw_input('Name: ')
    comment = raw_input('Kommentar: ')
    data.append(name)
    data.append(comment)
# besser:
data.append(raw_input('Name: '))
data.append(raw_input('Kommentar: '))
Du kannst Dir generell merken, dass man sich das Binden an einen Namen sparen kann, wenn man diesen nicht, oder nur genau einmal, verwendet. Ausnahmen sind hier Ausdrücke, die sonst zu lange werden und man durch das Trennen von Aufrufen ein wenig mehr Übersicht bekommt.

Wobei ich es hier umständlich finde, hier eine Liste zu generieren. Ich würde die Sequenz (und dann auch effizienter als Tupel!) erst on-the-fly im `execute` erstellen:

Code: Alles auswählen

my_cursor.execute('INSERT INTO guestbook VALUES (?, ?)', (name, comment))
Generell solltest Du darüber nachdenken, ob diese API, wie Du sie bisher hast, wirklich so flexibel ist! Du koppelst im Moment die Art und Weise der Dateneingabe an das Anlegen eines neuen Datensatzes - das würde ich trennen! Baue eine eigenständige Funktion `add_entry` o.ä. und erledige da nur das Speichern in die DB. Eine Benutzer-Eingabe-Funktion erstellst Du dann separat. Damit kannst Du auch auch anderen Wegen Einträge hinzufügen, etwa zum Testen o.ä. Später kommen Deine Daten dann ja wohl auch aus einem Formular - dann brauchst Du die Funktion zum Speichern nicht noch einmal umbauen, sondern kannst die so lassen, wie sie ist :-)

`my_db_connection` und `my_cursor` lassen "schlimmes" erahnen; wieso sind das zwei globale Variablen? Wieso übergibst Du diese nicht als Parameter?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo,
Aber... wieso zum Geier iterierst Du über diese Liste?
Dachte das wäre richtig so... Hatte ich hier gefunden: http://www.hdm-stuttgart.de/~maucher/Py ... QLite.html (Punkt 8.3) entweder is das auf der Seite Humbug, oder ich habe es falsch verstanden.
`my_db_connection` und `my_cursor` lassen "schlimmes" erahnen; wieso sind das zwei globale Variablen? Wieso übergibst Du diese nicht als Parameter?
Werd ich mir nochmal anschauen...
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

mcdaniels hat geschrieben: Dachte das wäre richtig so... Hatte ich hier gefunden: http://www.hdm-stuttgart.de/~maucher/Py ... QLite.html (Punkt 8.3) entweder is das auf der Seite Humbug, oder ich habe es falsch verstanden.
Du musst vor allem darüber nachdenken, ob denn Dein Code dem der Seite ähnelt! Wenn Du dort auf Abschnitt 8.3 anspielst, so sollte Dir der Unterschied in der Datenstruktur auffallen ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo,
ich hab das nun nochmals komplett umgebaut. Nun gibt es (nach meinem Verständnis) keine globalen Variablen mehr, denn ich rufe die jeweiligen Funktionen nur noch innerhalb der Funktion auf. Allerdings habe ich noch immer Probleme beim in die DB schreiben. Mein Insert Into mag mich nicht, wobei ich ja an sich jetzt ein Tupel übergebe. Ich bekomme allerdings diesmal auch keine Fehlermeldung. Es scheint zu funktionieren, aber in der DB landet nichts.

!!!Grade aufgefallen, mir fehlt ja der Cursor!!! -- sollte vielleicht mal ne Pause machen, seh schon den Wald vor lauter Bäumen nicht mehr :)


Hier mal mein Quelltext:

Code: Alles auswählen

import sqlite3
def db_connect():
    return sqlite3.connect("c:\guestbook.db")


def db_create():
    db_action = db_connect()
    db_action.execute('''CREATE TABLE if not exists guestbook
                         (name text, kommentar text)''')
    db_action.close()


def db_add_entry():
    entries = (raw_input("Name: "), raw_input("Kommentar: "))
    db_action = db_connect()
    db_action.execute('INSERT INTO guestbook VALUES(?,?)',entries)
    db_action.commit
    
    
Zuletzt geändert von mcdaniels am Dienstag 5. Juni 2012, 19:23, insgesamt 2-mal geändert.
deets

Ein bisschen besser. Aber

- deinem commit fehlen die Klammern
- man erzeugt eine Connection *EINMAL*, und dann uebergibst du die als Parameter
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hello,
danke - jetzt klappts... obwohl mir eigentlich der Cursor fehlt...?! (sollte wohl wie gesagt ne Pause machen)


LG
Daniel
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Nächste Version. Jetzt gibts noch ein Problem mit dem Encoding. Es wird wohl Unicode erwartet. Sende ich nun zb nun einen Umlaut an die DB wirft mit Python einen Programming Error. Ich möge die App doch bitte zu Unicode switchen. (Recherche läuft ... ;) )

Code: Alles auswählen

import sqlite3
def db_connect(db_path):
    return sqlite3.connect(db_path)


def db_create():
    db_action = db_connection.cursor()
    db_action.execute('''CREATE TABLE if not exists guestbook
                         (name text, kommentar text)''')
    db_action.close()


def db_add_entry():
    db_action = db_connection.cursor()
    db_action.execute('INSERT INTO guestbook VALUES(?,?)',
                      (raw_input("Name: "), raw_input("Kommentar: ")))
    db_connection.commit()
    db_connection.close()

db_connection = db_connect("c:/guestbook.db")
Benutzeravatar
gkuhl
User
Beiträge: 600
Registriert: Dienstag 25. November 2008, 18:03
Wohnort: Hong Kong

Hi mcdaniels,

habe deine Funktionen mal ausprobiert und es funktioniert doch eher suboptimal (ich hoffe du bist mit IPython vertraut):

Code: Alles auswählen

In [2]: conn = db_connect(':memory:')

In [3]: db_add_entry()
Name: bla
Kommentar: blub
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
/home/gerrit/<ipython-input-3-2f82cd18e3da> in <module>()
----> 1 db_add_entry()

/home/gerrit/<string> in db_add_entry()

NameError: global name 'db_connection' is not defined

In [4]: db_connection = db_connect(':memory:')

In [5]: db_add_entry()
Name: bla
Kommentar: blub
---------------------------------------------------------------------------
OperationalError                          Traceback (most recent call last)
/home/gerrit/<ipython-input-5-2f82cd18e3da> in <module>()
----> 1 db_add_entry()

/home/gerrit/<string> in db_add_entry()

OperationalError: no such table: guestbook

In [6]: db_create()

In [7]: db_add_entry()
Name: bla
Kommentar: blub

In [8]: db_add_entry()
Name: foo
Kommentar: flub
---------------------------------------------------------------------------
ProgrammingError                          Traceback (most recent call last)
/home/gerrit/<ipython-input-8-2f82cd18e3da> in <module>()
----> 1 db_add_entry()

/home/gerrit/<string> in db_add_entry()

ProgrammingError: Cannot operate on a closed database.
Wie hier schon mehrmals erwähnt solltest du das "Connections Objekt" als Parameter übergeben. Auch kannst du die Datenbank auch nicht einfach in einer Funktion schließen, da das zu ungewollten Seiteneffekten führt. Hier mal deine Funktion etwas abgewandelt:

Code: Alles auswählen

def db_add_entry(conn):
    db_action = conn.cursor()
    db_action.execute('INSERT INTO guestbook VALUES(?,?)', (raw_input("Name: "), raw_input("Kommentar: ")))
    conn.commit()
Grüße
Gerrit
mcdaniels
User
Beiträge: 168
Registriert: Mittwoch 18. August 2010, 19:53

Hallo,
nein IPython hab ich selbst noch nicht angewendet.

Betreffend das Connection Objekt als Parameter übergeben, sollte das doch so aussehen oder:

Code: Alles auswählen

def db_add_entry(db_connection):
    db_action = db_connection.cursor()
    db_action.execute('INSERT INTO guestbook VALUES(?,?)',
                      (raw_input("Name: "), raw_input("Kommentar: ")))
    db_connection.commit()
    

db_connection = db_connect("c:/guestbook.db")
db_connection = <sqlite3.Connection object at 0x0206F0B0>

Dann muss ich die Funktion aber so aufrufen:

Code: Alles auswählen

db_add_entry(db_connection)
damit es funktioniert.

@gkuhl: Kann es sein, dass es bei deinem Versuch daran scheitert, dass bei dir das Connection-Objekt nicht db_connection heisst, sondern nur conn du dann aber mein Programm verwendest in dem auf "db_connection" zugegriffen wird?

Könnte man den Cursor auch ausserhalb der Funktionen als globale Variable erstellen, oder gilt: Je weniger globale Variablen, desto besser?




LG
Daniel
Antworten