Umgang mit Datenbankverbindungen in einem Python-Bibliotheksmodul

23

Ich habe eine Bibliothek in Python erstellt, die Funktionen für den Zugriff auf eine Datenbank enthält. Dies ist eine Wrapperbibliothek für eine Drittanbieter-Anwendungsdatenbank, die aufgrund der Tatsache geschrieben wurde, dass die Drittanbieter-Anwendung keine anständige API bietet. Jetzt ließ ich ursprünglich jede Funktion eine Datenbankverbindung für die Dauer des Funktionsaufrufs öffnen, was in Ordnung war, bis meine Programmlogik verschachtelte Aufrufe der Funktionen verwendete, bei denen ich dann einige tausend Mal eine bestimmte Funktion aufrufen würde. Das war nicht sehr performant. Die Profilerstellung ergab, dass sich der Overhead im Datenbankverbindungsaufbau befand - einmal pro Funktionsaufruf. Also habe ich die offene Verbindung von innerhalb der Funktion (en) auf das Modul selbst verschoben, so dass die Datenbankverbindung geöffnet wurde, wenn das Bibliotheksmodul importiert wurde. Dies gab mir eine akzeptable Leistung.

Jetzt habe ich zwei Fragen dazu. Muss ich mir erstens Sorgen machen, dass ich die Datenbankverbindung nicht mehr explizit schließe und wie kann ich das mit dieser Einrichtung explizit tun? Zweitens: Fällt das, was ich getan habe, irgendwo in die Nähe des Bereichs der bewährten Verfahren und wie könnte ich das anders angehen?

leancz
quelle
1
Stellen Sie eine openConnFunktion bereit und lassen Sie den Benutzer sie an jede von ihm aufgerufene Funktion weitergeben, damit er den Zusammenhang in einer withAnweisung oder in einer anderen Anweisung erfassen kann
Daniel Gratzer,
1
Ich bin mit jozfeg einverstanden, überlege, eine Klasse zu erstellen, die die Datenbankverbindung innerhalb des Konstruktors öffnet und die Verbindung beim Beenden schließt
Nick Burns

Antworten:

31

Es hängt wirklich von der Bibliothek ab, die Sie verwenden. Einige von ihnen könnten die Verbindung selbst schließen (Hinweis: Ich habe die eingebaute sqlite3-Bibliothek überprüft, aber dies ist nicht der Fall). Python ruft einen Destruktor auf, wenn ein Objekt den Gültigkeitsbereich verlässt, und diese Bibliotheken implementieren möglicherweise einen Destruktor, der die Verbindungen ordnungsgemäß schließt.

Dies ist jedoch möglicherweise nicht der Fall! Ich würde empfehlen, wie andere in den Kommentaren, es in ein Objekt zu wickeln.

class MyDB(object):

    def __init__(self):
        self._db_connection = db_module.connect('host', 'user', 'password', 'db')
        self._db_cur = self._db_connection.cursor()

    def query(self, query, params):
        return self._db_cur.execute(query, params)

    def __del__(self):
        self._db_connection.close()

Dadurch wird Ihre Datenbankverbindung zu Beginn instanziiert und geschlossen, wenn der Bereich, in dem Ihr Objekt instanziiert wurde, nicht mehr gültig ist. Hinweis: Wenn Sie dieses Objekt auf Modulebene instanziieren , bleibt es für Ihre gesamte Anwendung bestehen. Sofern dies nicht beabsichtigt ist, würde ich vorschlagen, Ihre Datenbankfunktionen von den Nicht-Datenbankfunktionen zu trennen.

Glücklicherweise hat Python die Datenbank-API standardisiert , sodass dies mit allen kompatiblen DBs für Sie funktioniert :)

Travis
quelle
Wie vermeiden Sie , dass selfin def query(self,?
Samayo
2
definieren vermeiden? Self ist das, was dies als Instanzmethode und nicht als Klassenmethode definiert. Ich vermute, Sie könnten die Datenbank als statische Eigenschaft für die Klasse erstellen und dann nur Klassenmethoden verwenden (die nirgendwo benötigt werden), aber dann wäre die Datenbank für die Klasse global und nicht nur die individuelle Instanziierung derselben.
Travis
Ja, weil ich versucht habe, Ihr Beispiel zu verwenden, um eine einfache Abfrage zu erstellen, db.query('SELECT ...', var)und es sich darüber beschwert hat, ein drittes Argument zu benötigen.
Samayo
@samson, Sie müssen zuerst das MyDBObjekt instanziieren :db = MyDB(); db.query('select...', var)
cowbert
Dies verhinderte NachrichtenResourceWarning: unclosed <socket.socket...
Bob Stein
3

Beim Umgang mit Datenbankverbindungen sind zwei Dinge zu beachten:

  1. Vermeiden Sie mehrfache Verbindungsinstanziierungen. Das Öffnen einer Datenbankverbindung durch jede Funktion wird als unangemessen angesehen. Bei der begrenzten Anzahl von Datenbanksitzungen würden Ihnen die Sitzungen ausgehen. Zumindest würde Ihre Lösung nicht skalieren. Verwenden Sie stattdessen das Singleton-Muster. Ihre Klasse würde nur einmal instanziiert. Weitere Informationen zu diesem Muster finden Sie unter Link

  2. Schließen Sie die Verbindung nach dem Beenden der App, nehmen wir an, Sie haben dies nicht getan, und Sie haben mindestens ein Dutzend Instanzen der App, die das Gleiche tun. Zuerst würde alles gut gehen, aber Ihnen würden die Datenbanksitzungen ausgehen und die einzige Lösung Es wäre, den Datenbankserver neu zu starten, was für eine Live-App nicht gut ist. Verwenden Sie daher nach Möglichkeit dieselbe Verbindung.

Um alle diese Konzepte zu verfestigen, sehen Sie sich das folgende Beispiel an, das psycopg2 umschließt

import psycopg2


class Postgres(object):
"""docstring for Postgres"""
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
            # normally the db_credenials would be fetched from a config file or the enviroment
            # meaning shouldn't be hardcoded as follow
            db_config = {'dbname': 'demo', 'host': 'localhost',
                     'password': 'postgres', 'port': 5432, 'user': 'postgres'}
            try:
                print('connecting to PostgreSQL database...')
                connection = Postgres._instance.connection = psycopg2.connect(**db_config)
                cursor = Postgres._instance.cursor = connection.cursor()
                cursor.execute('SELECT VERSION()')
                db_version = cursor.fetchone()

            except Exception as error:
                print('Error: connection not established {}'.format(error))
                Postgres._instance = None

            else:
                print('connection established\n{}'.format(db_version[0]))

        return cls._instance

    def __init__(self):
        self.connection = self._instance.connection
        self.cursor = self._instance.cursor

    def query(self, query):
        try:
            result = self.cursor.execute(query)
        except Exception as error:
            print('error execting query "{}", error: {}'.format(query, error))
            return None
        else:
            return result

    def __del__(self):
        self.connection.close()
        self.cursor.close()
Ponach
quelle
1
Hallo! Vielen Dank für Ihre Antwort. Aber wenn ich versuche, es in meinem Fall umzusetzen, habe ich if Database._instance is None: NameError: name 'Database' is not defined. Ich kann nicht verstehen, was Databaseist und wie ich das beheben könnte.
Ilya Rusin
1
@IlyaRusin, das war meine Schuld, in der Tat ist Database nur eine übergeordnete Klasse, in der ich gemeinsame Methoden für unterschiedliche RDBMS-Behandlung, da ich nicht nur mit Postgres verbinden. Wie auch immer, entschuldigen Sie den Fehler und ich hoffe, dass die korrigierte Version für Sie nützlich sein kann. Sie können den Code jederzeit hinzufügen und an Ihre Bedürfnisse anpassen, wenn Sie eine verwandte Frage haben, zögern Sie nicht.
Ponach
Wenn ich Postgres.query(Postgres(), some_sql_query)eine whileSchleife wiederholt aufrufen würde, würde sie dann die Verbindung in jeder Iteration öffnen und schließen oder sie für die gesamte Zeit der whileSchleife offen halten, bis das Programm beendet wird?
@Michael die Verbindungsklasse ist als Singleton implementiert, daher würde sie nur einmal instanziiert, aber insgesamt würde ich gegen die vorgeschlagene Art des Aufrufs empfehlen, anstatt es in einer Variablen zu initiieren
Ponach
1
@ponach Danke, es macht genau das, was ich erreichen wollte. Ich habe Ihren Code ein wenig angepasst und versucht, eine UPDATE-Anweisung in Ihrer query()Funktion zu verwenden, aber es scheint ein Problem mit meinem Code zu geben, wenn ich meine App "parallel" ausführe. Ich habe eine separate Frage dazu gestellt: softwareengineering.stackexchange.com/questions/399582/…
2

Es wäre interessant, Kontext-Manager-Funktionen für Ihre Objekte anzubieten. Dies bedeutet, dass Sie einen Code wie diesen schreiben können:

class MyClass:
    def __init__(self):
       # connect to DB
    def __enter__(self):
       return self
    def __exit__(self):
       # close the connection

Dies bietet Ihnen eine praktische Möglichkeit, die Verbindung zur Datenbank automatisch zu schließen, indem Sie die Klasse mit der with-Anweisung aufrufen:

with MyClass() as my_class:
   # do what you need
# at this point, the connection is safely closed.
Billal Begueradj
quelle
-1

Eine lange Zeit, um darüber nachzudenken. Heute habe ich den Weg gefunden. Ich weiß nicht, ob es der beste Weg ist. Sie erstellen eine Datei mit dem Namen: conn.py und speichern sie im Ordner /usr/local/lib/python3.5/site-packages/conn/. Ich benutze freebsd und dies ist der Pfad meines Site-Packages-Ordners. in meiner conn.py: conn = "dbname = omnivore user = postgres password = 12345678"

"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" und in dem Skript, das ich Verbindung aufrufen möchte, schreibe ich:

psycopg2 importieren psycopg2.extras importieren psycopg2.extensions

von conn importiere conn versuche: conn = psycopg2.connect (conn.conn) außer: page = "Kann nicht auf die Datenbank zugreifen"

cur = conn.cursor ()

und bla bla ....

Ich hoffe, das ist nützlich

Phong
quelle