Cara menangani koneksi basis data dalam modul perpustakaan Python

23

Saya telah membuat perpustakaan dengan Python yang berisi fungsi untuk mengakses database. Ini adalah pustaka pembungkus di sekitar basis data aplikasi pihak ketiga, ditulis karena fakta bahwa aplikasi pihak ketiga tidak menawarkan API yang layak. Sekarang saya awalnya membiarkan setiap fungsi membuka koneksi database selama durasi panggilan fungsi yang OK, sampai logika program saya menggunakan panggilan bersarang ke fungsi di mana saya kemudian akan memanggil fungsi tertentu beberapa ribu kali. Ini bukan sangat performant. Profiling ini menunjukkan bahwa overhead dalam pengaturan koneksi database - sekali per panggilan fungsi. Jadi saya memindahkan koneksi terbuka dari dalam fungsi ke modul itu sendiri, sehingga koneksi database akan dibuka ketika modul perpustakaan diimpor. Ini memberi saya kinerja yang dapat diterima.

Sekarang saya punya dua pertanyaan tentang ini. Pertama, apakah saya perlu khawatir bahwa saya tidak lagi secara eksplisit menutup koneksi database dan bagaimana saya bisa melakukannya secara eksplisit dengan pengaturan ini? Kedua, apakah yang telah saya lakukan berada di dekat bidang praktik yang baik dan bagaimana saya bisa mendekati ini?

leancz
sumber
1
Berikan openConnfungsi dan buat pengguna meneruskannya ke setiap fungsi yang mereka sebut, dengan cara itu mereka dapat mengatur koneksi dalam withpernyataan atau apa pun
Daniel Gratzer
1
Saya setuju dengan jozfeg, pertimbangkan membuat kelas yang membuka koneksi db di dalam konstruktor, dan yang menutup koneksi saat keluar
Nick Burns

Jawaban:

31

Ini sangat tergantung pada perpustakaan yang Anda gunakan. Beberapa dari mereka mungkin menutup koneksi sendiri (Catatan: Saya memeriksa perpustakaan sqlite3 builtin, dan itu tidak). Python akan memanggil destruktor ketika suatu objek keluar dari ruang lingkup, dan perpustakaan ini mungkin menerapkan destruktor yang menutup koneksi dengan anggun.

Namun, itu mungkin tidak terjadi! Saya akan merekomendasikan, seperti yang orang lain miliki di komentar, untuk membungkusnya dalam suatu objek.

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()

Ini akan membuat koneksi database Anda di awal, dan menutupnya ketika tempat objek Anda dipakai keluar dari ruang lingkup. Catatan: Jika Anda instantiate objek ini di tingkat modul, itu akan bertahan untuk seluruh aplikasi Anda. Kecuali jika ini dimaksudkan, saya sarankan memisahkan fungsi basis data Anda dari fungsi non-basis data.

Untungnya, python telah menstandarkan API Database , jadi ini akan bekerja dengan semua DB yang sesuai untuk Anda :)

Travis
sumber
Bagaimana Anda menghindari itu selfdi def query(self,?
samayo
2
mendefinisikan menghindari? Diri adalah apa yang mendefinisikan itu sebagai metode instan, bukan metode kelas. Saya kira Anda bisa membuat database sebagai properti statis di kelas, dan kemudian hanya menggunakan metode kelas (tidak diperlukan sendiri di mana pun), tetapi kemudian database akan bersifat global ke kelas, bukan hanya instantiasi individu.
Travis
Ya, karena saya mencoba menggunakan contoh Anda untuk membuat kueri sederhana db.query('SELECT ...', var)dan mengeluhkan perlunya argumen ketiga.
samayo
@samson, Anda harus instantiate MyDBobjek terlebih dahulu:db = MyDB(); db.query('select...', var)
cowbert
Ini mencegah pesanResourceWarning: unclosed <socket.socket...
Bob Stein
3

saat menangani koneksi basis data ada dua hal yang harus diperhatikan:

  1. mencegah beberapa koneksi instantiations, membiarkan setiap fungsi membuka koneksi database dianggap praktik buruk, memberikan jumlah terbatas sesi database, Anda akan kehabisan sesi; setidaknya solusi Anda tidak akan menurun, alih-alih, gunakan pola tunggal, kelas Anda akan dipakai hanya sekali, untuk informasi lebih lanjut tentang pola ini, lihat tautan

  2. menutup koneksi saat keluar dari aplikasi, katakanlah Anda tidak melakukannya, dan bahwa Anda memiliki paling tidak selusin instance aplikasi yang melakukan hal yang sama, pada awalnya semuanya akan berjalan dengan baik, tetapi Anda akan kehabisan sesi basis data, dan satu-satunya perbaikan akan me-restart server database, yang bukan hal yang baik untuk aplikasi live karenanya gunakan koneksi yang sama bila memungkinkan.

untuk memperkuat semua konsep ini lihat contoh berikut yang membungkus psycopg2

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
sumber
1
Hai! Terima kasih atas jawaban Anda. Tetapi ketika saya mencoba menerapkannya dalam kasus saya, saya punya if Database._instance is None: NameError: name 'Database' is not defined. Saya tidak mengerti apa Databaseitu dan bagaimana saya bisa memperbaikinya.
Ilya Rusin
1
@IlyaRusin itu salah saya, sebenarnya Database hanyalah kelas induk di mana saya meletakkan metode umum untuk penanganan RDBMS yang berbeda karena saya terhubung tidak hanya ke Postgres. Namun maaf atas kesalahan ini dan saya harap versi yang diperbaiki dapat bermanfaat bagi Anda, silakan tambahkan, modifikasi kode sesuai kebutuhan Anda jika Anda memiliki pertanyaan terkait jangan ragu.
ponach
Jika saya berulang kali akan memanggil Postgres.query(Postgres(), some_sql_query)dalam whilelingkaran, akan tetap membuka dan menutup sambungan dalam setiap iterasi, atau tetap terbuka untuk seluruh waktu dari whilelingkaran sampai keluar program?
@Michael kelas koneksi diimplementasikan sebagai singleton, maka itu akan dipakai hanya satu kali, tetapi secara keseluruhan saya akan merekomendasikan terhadap cara panggilan yang disarankan, alih-alih menginisiasinya dalam variabel
ponach
1
@ balas Terima kasih, ia melakukan persis apa yang ingin saya capai. Saya mengadaptasi kode Anda sedikit dan mencoba menggunakan pernyataan UPDATE dalam query()fungsi Anda , tetapi tampaknya ada masalah dengan kode saya ketika saya menjalankan aplikasi saya di "paralel". Saya membuat pertanyaan terpisah tentang hal itu: softwareengineering.stackexchange.com/questions/399582/…
2

Akan menarik untuk menawarkan kemampuan manajer konteks untuk objek Anda. Ini berarti Anda dapat menulis kode seperti ini:

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

Ini akan menawarkan Anda cara praktis untuk menutup koneksi ke database secara otomatis dengan memanggil kelas menggunakan pernyataan with:

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

Waktu yang lama untuk memikirkan hal ini. Hari ini, saya menemukan jalannya. saya tidak tahu itu cara terbaik. Anda membuat file dengan nama: conn.py, dan menyimpannya di folder /usr/local/lib/python3.5/site-packages/conn/. Saya menggunakan freebsd dan ini adalah path folder situs-paket saya. di conn.py saya: conn = "dbname = omnivore user = postgres password = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` ` dan dalam skrip saya ingin menelepon koneksi, saya menulis:

import psycopg2 import psycopg2.extras import psycopg2.extensions

dari conn import conn try: conn = psycopg2.connect (conn.conn) kecuali: halaman = "Tidak dapat mengakses database"

cur = conn.cursor ()

dan bla bla ....

semoga ini bermanfaat

phong
sumber