Datenbankabfrage mit with?

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
Miranda
User
Beiträge: 23
Registriert: Sonntag 23. September 2018, 21:45

Samstag 16. Januar 2021, 01:29

Hallo und einen schönen Abend :-)

Eine normale Abfrage mache ich ja ungefähr so:

Code: Alles auswählen

    import sqlite3
    con = sqlite3.connect("data.db")
    cursor = con.cursor()
    
    sql = '''INSERT INTO ...'''
    cursor.execute(sql)
    con.commit()
    con.close() 
Aber wie geht das mit with?

Code: Alles auswählen

      import sqlite3
      with sqlite3.connect("data.db") as con:
            sql = "SELECT * FROM ..."
            con.execute(sql)
            if con.fetchone() != None:
                mach_etwas()
Da bekomme ich immer einen Fehler: AttributeError: 'sqlite3.Connection' object has no attribute 'fetchone'

Was mache ich falsch?

Ganz liebe Grüße an euch :-)
Benutzeravatar
__blackjack__
User
Beiträge: 8823
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Samstag 16. Januar 2021, 01:36

@Miranda: Du versuchst etwas mit der Verbindung zu machen wofür ein Cursor gebraucht wird. Im ersten Code machst Du es ja richtig.
Q: What is the volume of a pizza of radius z and thickness a?
A: pi·z·z·a
Miranda
User
Beiträge: 23
Registriert: Sonntag 23. September 2018, 21:45

Samstag 16. Januar 2021, 08:50

Ach so, den Cursor brauche ich nach wie vor? Ich hatte es so verstanden, als würde ich mir den dank dem "with" sparen können? :shock:
Also ist die Ersparnis mit "with" ja gar nicht so groß, wie ich erst dachte :?

Vielen lieben Dank!
Benutzeravatar
__blackjack__
User
Beiträge: 8823
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Samstag 16. Januar 2021, 11:20

@Miranda: Bei dem ``with`` geht es nicht um Ersparnis sondern um Sicherheit. Also in dem Zusammenhang spart man natürlich doch, denn wenn man es ohne das ``with`` genau so sicher machen will, braucht man mehr Code als in Deinem ersten Beispiel. Sicher ohne ``with``:

Code: Alles auswählen

#!/usr/bin/env python3
import sqlite3


def main():
    connection = sqlite3.connect("data.db")
    try:
        cursor = connection.cursor()
        try:
            cursor.execute("SELECT * FROM …")
            for record in cursor.fetchall():
                print(record)
            connection.commit()
        finally:
            cursor.close()
    finally:
        connection.close()


if __name__ == "__main__":
    main()
Sicher mit ``with`` hat fast die Hälfte weniger Zeilen:

Code: Alles auswählen

#!/usr/bin/env python3
import sqlite3
from contextlib import closing


def main():
    with closing(sqlite3.connect("data.db")) as connection:
        with closing(connection.cursor()) as cursor:
            cursor.execute("SELECT * FROM …")
            for record in cursor.fetchall():
                print(record)
            connection.commit()


if __name__ == "__main__":
    main()
Ich benutze `contextlib.closing()` auch für die Verbindung weil es a) nicht zur DB-API-Spezifikation gehört, dass man die Verbindung als Kontextmanager verwenden kann, und b) das Verhalten davon auch überraschend ist, denn das sorgt für `commit()`/`rollback()` und schliesst nicht etwa die Verbindung.
Q: What is the volume of a pizza of radius z and thickness a?
A: pi·z·z·a
Sirius3
User
Beiträge: 14760
Registriert: Sonntag 21. Oktober 2012, 17:20

Samstag 16. Januar 2021, 11:47

@__blackjack__: was man als "Kontext" empfindet, ist halt unterschiedlich. Datenbankverbindungen sind etwas anderes als geöffnete Dateien.
Eine Datenbank wird einmal geöffnet und dann macht man verschiedene Transaktionen.
Ein der Kontext hier ist eine Transaktion, also etwas, das man mit commit abschließt.

Code: Alles auswählen

def transaction(connection):
    try:
        cursor = connection.cursor()
        try:
            cursor.execute("INSERT INTO …")
        finally:
            cursor.close()
    except:
        connection.rollback()
        raise
    else:
        connection.commit()
Und das vereinfacht der Kontextmanager von sqlite3:

Code: Alles auswählen

def transaction(connection):
    with connection:
        cursor = connection.cursor()
        try:
            cursor.execute("INSERT INTO …")
        finally:
            cursor.close()
Ich hätte es sinnvoller gefunden, wenn der Kontextmanager so ausgesehen hätte:

Code: Alles auswählen

class Transaction:
    def __init__(self, connection):
        self.connection = connection
        self.cursor = None

    def __enter__(self):
        self.cursor = self.connection.cursor()
        return self.cursor

    def __exit__(self, type, value, traceback):
        self.cursor.close()
        if type is None:
            self.connection.commit()
        else:
            self.connection.rollback()


with Transaction(connection) as cursor:
    cursor.execute("INSERT INTO …")
Benutzeravatar
__blackjack__
User
Beiträge: 8823
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Samstag 16. Januar 2021, 13:47

@Sirius3: Kontextmanager werden ja üblicherweise zum Aufräumen verwendet und das muss man bei einer Verbindung ja auch. In SQLAlchemy ist die `Engine` ein Kontextmanager zum schliessen der Verbindung und hat eine `begin()`-Methode die einen Kontextmanager für eine Transaktion liefert. So etwas hätte ich bei `sqlite3` für sinnvoller empfunden, denn sonst hat man immer eine der beiden Sachen, die man mit ``try``/(``except``)/``finally`` lösen muss, statt für beides Kontextmanager zu haben.
Q: What is the volume of a pizza of radius z and thickness a?
A: pi·z·z·a
Benutzeravatar
DeaD_EyE
User
Beiträge: 638
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Montag 18. Januar 2021, 09:36

Der Cursor muss auch geschlossen werden?
Das ist das erste mal, dass ich diese Methode überhaupt sehe.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
pillmuncher
User
Beiträge: 1268
Registriert: Samstag 21. März 2009, 22:59
Wohnort: München

Montag 18. Januar 2021, 10:12

@DeaD_EyE: Ein Cursor sollte immer sobald als möglich geschlossen werden, damit andere User/Prozesse nicht warten oder mit veralteten Daten arbeiten müssen. Je nach Isolation Level halt.
In specifications, Murphy's Law supersedes Ohm's.
DasIch
User
Beiträge: 2641
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Montag 18. Januar 2021, 13:14

Andere User/Prozesse werden auch nachdem der Cursor geschlossen ist noch weiter warten müssen bis die Transaktion zu Ende ist. Cursor werden am Ende einer Transaktion bei postgres implizit geschlossen.

fun fact: Die SQLite Dokumentation hat die notwendige Syntax für Cursor gar nicht dokumentiert. Ich hab mal kurz in den Code reingeschaut und festgestellt dass das auch tatsächlich kein serverseitiger Cursor geschlossen wird. Es passiert aber schon was dessen Zweck ich jetzt nicht tiefer ergründet habe, irgendwas mit prepared statements.

Wahrscheinlich nicht verkehrt close explizit aufzurufen, passiert sonst über __del__.
Benutzeravatar
DeaD_EyE
User
Beiträge: 638
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Montag 18. Januar 2021, 13:48

In der Python-Dokumentation:

https://docs.python.org/3/library/sqlit ... or-objects
close()

Close the cursor now (rather than whenever __del__ is called).

The cursor will be unusable from this point forward; a ProgrammingError exception will be raised if any operation is attempted with the cursor.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
pillmuncher
User
Beiträge: 1268
Registriert: Samstag 21. März 2009, 22:59
Wohnort: München

Montag 18. Januar 2021, 14:40

@DasIch: Ich sollte kurz nachdem Aufwachen noch nicht posten.
In specifications, Murphy's Law supersedes Ohm's.
Antworten