Python kütüphane modülünde veritabanı bağlantıları ile nasıl baş edilir


23

Python'da bir veritabanına erişmek için işlevler içeren bir kütüphane yarattım. Bu, üçüncü taraf uygulamasının iyi bir API sunmaması nedeniyle yazılmış, üçüncü taraf bir uygulama veritabanı etrafındaki bir sarmalayıcı kitaplığıdır. Şimdi başlangıçta her fonksiyonun tamam olduğu fonksiyon çağrısı boyunca bir veritabanı bağlantısı açmasına izin verdim, program mantığım içiçe çağrıları kullanana kadar, sonra belirli bir fonksiyonu birkaç bin defa arayacağım. Bu çok iyi performans göstermedi. Bu profil oluşturma, ek yükün veritabanı bağlantısı kurulumunda olduğunu gösterdi - işlev çağrısı başına bir kez. Böylece açık bağlantıyı işlevlerin içinden modülün kendisine taşıdım, böylece kütüphane modülü içe aktarıldığında veritabanı bağlantısı açılacaktı. Bu bana kabul edilebilir bir performans verdi.

Şimdi bununla ilgili iki sorum var. Öncelikle, artık veritabanı bağlantısını açıkça kapatmayacağım ve bu kurulumda açıkça nasıl yapabilirim? İkincisi, yaptığım şey iyi uygulama alanına yakın bir yere mi düşüyor ve başka türlü nasıl yaklaşabilirim?


1
Bir openConnişlev sağlayın ve kullanıcının aradığı her bir işleve geçmesini sağlayın , bu şekilde bağlantıyı bir withifadeyle ya da her neyse
kapsamlandırabilirler

1
Jozfeg ile aynı fikirdeyim, kurucu içindeki db bağlantısını açan ve çıkıştaki bağlantıyı kapatan bir sınıf yaratmayı düşünün
Nick Burns

Yanıtlar:


31

Bu gerçekten kullandığınız kütüphaneye bağlıdır. Bazıları bağlantıyı kendi başlarına kapatabilirdi (Not: Yerleşik sqlite3 kütüphanesini kontrol ettim, yok). Bir nesne kapsam dışına çıktığında Python bir yıkıcı diyecek ve bu kütüphaneler bağlantıları dikkatlice kapatan bir yıkıcı uygulayabilecektir.

Ancak bu durum böyle olmayabilir! Başkalarının yorumlarda olduğu gibi onu bir nesneye sarmasını tavsiye ederim.

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

Bu, başlangıçta veritabanı bağlantınızı somutlaştırır ve nesnenizin başlatıldığı yer kapsam dışı kaldığında kapatılır. Not: Bu nesneyi modül düzeyinde başlatırsanız , uygulamanızın tamamı için geçerli olur. Bu amaçlanmadıkça, veritabanı işlevlerinizi veritabanı olmayan işlevlerden ayırmanızı öneririm.

Neyse ki, python Veritabanı API'sini standartlaştırdı , bu yüzden bu sizin için tüm uyumlu DB'lerle çalışacak :)


Bunu nasıl engelleyebilirim selfiçinde def query(self,?
samayo

2
kaçınmayı tanımlamak? Benlik, bunu bir sınıf metodu yerine örnek metodu olarak tanımlar. Veritabanını sınıfta statik bir özellik olarak yaratabileceğinizi ve daha sonra sadece sınıf yöntemlerini kullanabileceğinizi (hiçbir yerde kendi kendine gerekli olmamayacağınızı) sanırım, ancak daha sonra veritabanı sınıfa yöneliktir;
Travis,

Evet, çünkü örneğinizi basit bir sorgu yapmak için kullanmaya çalıştım db.query('SELECT ...', var)ve üçüncü bir tartışmaya ihtiyaç duyulduğundan şikayet etti.
samayo

@ samson, MyDBönce nesneyi somutlaştırmanız gerekir :db = MyDB(); db.query('select...', var)
cowbert

Bu engellenen mesajlarResourceWarning: unclosed <socket.socket...
Bob Stein

3

Veri tabanı bağlantılarını kullanırken dikkat edilmesi gereken iki şey vardır:

  1. Birden fazla bağlantının başlatılmasını engellemek, her bir işlevin bir veritabanı bağlantısını açmasına izin vermek, sınırlı sayıda veritabanı oturumu vererek, oturumların tükeneceğini; en azından çözümünüz ölçeklenemez, bunun yerine, singleton desenini kullanın, sınıfınız yalnızca bir kez örneklenir, bu desen hakkında daha fazla bilgi için linke bakınız.

  2. Uygulamadan çıktıktan sonra bağlantıyı kapatma, diyelim ki yapmadınız ve aynı şeyi yapan uygulamanın en azından bir düzine örneğine sahip olduğunuzu varsayalım, ilk başta her şey yolunda gider, ancak veritabanı oturumları tükenir ve tek düzeltme Canlı bir uygulama için iyi bir şey olmayan veritabanı sunucusunu yeniden başlatmak, dolayısıyla mümkün olduğunda aynı bağlantıyı kullanmak olacaktır.

Bütün bu kavramları sağlamlaştırmak için, psycopg2'yi saran aşağıdaki örneğe bakınız.

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

1
Merhaba! Cevabınız için teşekkür ederim. Ama benim durumumda uygulamaya çalıştığımda if Database._instance is None: NameError: name 'Database' is not defined. Ne Databaseolduğunu ve bunu nasıl düzeltebileceğimi anlayamıyorum .
Ilya Rusin

1
Benim hatam olan @IlyaRusin, aslında Veri Tabanı sadece Postgres'e bağlandığım için farklı RDBMS işlemlerinde ortak yöntemler kullandığım bir üst sınıftır. Ancak hata için özür dilerim ve düzeltilmiş sürümün sizin için yararlı olabileceğini umuyorum, eklemek için çekinmeyin, ilgili herhangi bir sorunuz varsa tereddüt etmeyin.
ponach

Tekrar tekrar Postgres.query(Postgres(), some_sql_query)bir whiledöngüde ararsam, her yinelemede bağlantıyı açar ve kapatır mı, yoksa whileprogram çıkana kadar döngünün tüm zamanı boyunca açık tutar mı?

Bağlantı sınıf bir singleton olarak uygulanmaktadır @Michael, dolayısıyla sadece bir kez kullanacağımız, ama genel ı yerine değişken bunu başlatmak, çağrının önerilen şekilde karşı öneriyoruz
ponach

1
@ ponach Teşekkürler, tam olarak istediğim şeyi yapıyor. Kodunuzu biraz uyarladım ve query()işlevinizde bir UPDATE deyimi kullanmaya çalıştım , ancak uygulamamı "paralel" olarak çalıştırdığımda kodumla ilgili bir sorun var gibi görünüyor. Bununla ilgili ayrı bir soru yaptım: softwareengineering.stackexchange.com/questions/399582/…

2

Nesneleriniz için bağlam yöneticisi yetenekleri sunmak ilginç olurdu. Bu, şöyle bir kod yazabileceğiniz anlamına gelir:

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

Bu size, with ifadesini kullanarak sınıfı çağırarak veritabanına otomatik olarak bağlanmayı kapatmanın kullanışlı bir yolunu sunar:

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

-1

Bunun hakkında düşünmek için uzun zaman. Günün yolunu buldum. Bilmiyorum, en iyi yol bu. conn.py isimli bir dosya yaratır ve /usr/local/lib/python3.5/site-packages/conn/ klasörüne kaydedersiniz. Freebsd kullanıyorum ve bu site paketleri klasörümün yolu. benim conn.py içinde: conn = "dbname = çok kullanıcılı kullanıcı = postgres şifre = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` komut dosyasında bağlantıyı aramak istiyorum:

psycopg2 alma psycopg2.extras alma psycopg2.extensions alma

conn import conn den dene: conn = psycopg2.connect (conn.conn) haricinde: page = "Veritabanına erişilemiyor"

cur = conn.cursor ()

ve filan filan ....

umarım bu yararlı

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.