Pythonライブラリモジュールでデータベース接続を処理する方法


23

データベースにアクセスするための関数を含むライブラリをPythonで作成しました。これは、サードパーティアプリケーションが適切なAPIを提供しないという事実のために書かれた、サードパーティアプリケーションデータベースのラッパーライブラリです。今では、プログラムロジックが特定の関数を数千回呼び出す関数のネストされた呼び出しを使用するまで、関数呼び出しの期間中は各関数がデータベース接続を開くようにしました。これはあまりパフォーマンスがよくありませんでした。これをプロファイリングすると、オーバーヘッドがデータベース接続セットアップにあることがわかりました(関数呼び出しごとに1回)。そこで、ライブラリモジュールがインポートされたときにデータベース接続が開かれるように、関数内からモジュール自体に開いた接続を移動しました。これにより、許容できるパフォーマンスが得られました。

これに関して、2つの質問があります。まず、データベース接続を明示的に閉じる必要がなくなり、このセットアップでどのように明示的に行うことができるかを心配する必要がありますか?第二に、私がやったことは、優れた実践の領域に近いどこに落ちますか?


1
提供openConn、彼らはスコープに接続できる方法の機能を、ユーザーが、彼らが呼んで各関数に渡し作るwith文またはものは何でも
ダニエルGratzer

1
私は、コンストラクタ内のDB接続をオープンしたクラスを作成することを検討し、jozfegに同意し、それは出口の接続を閉じ
ニック・バーンズ

回答:


31

実際に使用しているライブラリに依存します。それらのいくつかは、自分で接続を閉じることができます(注:組み込みのsqlite3ライブラリをチェックしましたが、チェックしません)。Pythonは、オブジェクトがスコープから外れるとデストラクタを呼び出します。これらのライブラリは、接続を適切に閉じるデストラクタを実装する場合があります。

しかし、そうではないかもしれません!他の人がコメントで述べているように、オブジェクトにラップすることをお勧めします。

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

これにより、開始時にデータベース接続がインスタンス化され、オブジェクトがインスタンス化された場所がスコープ外になったときにデータベース接続が閉じられます。注:このオブジェクトをモジュールレベルでインスタンス化すると、アプリケーション全体で保持されます。これが意図されていない限り、データベース関数を非データベース関数から分離することをお勧めします。

幸いなことに、PythonはDatabase API標準化したので、これはすべての準拠DBで動作します:)


どのようにあなたはそれを避けるかselfdef query(self,
サマヨ

2
回避を定義しますか?Selfは、クラスメソッドではなく、インスタンスメソッドとしてそれを定義するものです。データベースをクラスの静的プロパティとして作成し、クラスメソッドのみを使用できます(自己はどこにも必要ありません)が、データベースは個々のインスタンス化だけでなく、クラスに対してグローバルになります。
トラビス

ええ、私はあなたの例を使って簡単なクエリを作成しようとしましたdb.query('SELECT ...', var)が、3番目の引数が必要だと文句を言いました。
サマヨ

@samson、あなたはインスタンス化する必要があるMyDB最初のオブジェクト:db = MyDB(); db.query('select...', var)
cowbert

これによりメッセージが防止されましたResourceWarning: unclosed <socket.socket...
ボブスタイン

3

データベース接続を処理する際、次の2つの点に注意する必要があります。

  1. 複数の接続のインスタンス化を防止し、各機能がデータベース接続を開くのは悪い習慣と見なされ、データベースセッションの数を制限すると、セッションが不足します。少なくともソリューションはスケールアウトせず、代わりにシングルトンパターンを使用し、クラスは1回だけインスタンス化されます。このパターンの詳細についてはリンクを参照してください

  2. アプリの終了時に接続を閉じたとします。そうしないと、同じことを実行しているアプリのインスタンスが少なくとも12個あります。最初はすべて正常に動作しますが、データベースセッションが不足します。データベースサーバーを再起動することです。これは、ライブアプリには適していません。したがって、可能な限り同じ接続を使用してください。

これらのすべての概念を固めるには、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()

1
こんにちは!ご回答ありがとうございます。しかし、私が私の場合にそれを実装しようとすると、私は持っていif Database._instance is None: NameError: name 'Database' is not definedます。これが何でDatabaseあり、どのように修正できるのか理解できません。
イリヤRusin

1
@IlyaRusinが私のせいでした。実際、データベースは単なる親クラスであり、Postgresに接続するだけでなく、さまざまなRDBMS処理に共通のメソッドを配置しています。しかし、間違いをおかけして申し訳ありません。関連する質問がある場合は、遠慮なく追加し、必要に応じてコードを修正してください。
ポナッハ

私は何度呼んでしまう場合Postgres.query(Postgres(), some_sql_query)whileループ、それはまだオープンし、各反復でクローズ接続、またはそれは全体の時間のために開いたままになりwhile、プログラムが終了するまでループ?

@Michael接続クラスはシングルトンとして実装されるため、1回だけインスタンス化されますが、全体的には推奨される呼び出し方法に反して、代わりに変数で開始することをお勧めします
ponach

1
@ponachおかげで、まさに私が達成したかったことができます。コードを少し修正し、query()関数でUPDATEステートメントを使用しようとしましたが、アプリを「並列」で実行するとコードに問題があるようです。:私はそれについての個別のご質問作らsoftwareengineering.stackexchange.com/questions/399582/...

2

オブジェクトのコンテキストマネージャ機能を提供することは興味深いでしょう。これは、次のようなコードを記述できることを意味します。

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

これにより、withステートメントを使用してクラスを呼び出すことにより、データベースへの接続を自動的に閉じる便利な方法が提供されます。

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

-1

これについて考えるのに長い時間。今日まで、私は道を見つけた。私はそれが最良の方法だとは知りません。conn.pyという名前のファイルを作成し、/ usr / local / lib / python3.5 / site-packages / conn /フォルダーに保存します。私はfreebsdを使用し、これは私のサイトパッケージフォルダーのパスです。conn.py:conn = "dbname = omnivore user = postgres password = 12345678"

`` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` `` ``そして、私は接続を呼び出したいスクリプトで、私は書きます:

import psycopg2 import psycopg2.extras import psycopg2.extensions

conn import conn tryから:conn = psycopg2.connect(conn.conn)例外:page =「データベースにアクセスできません」

cur = conn.cursor()

何とか…

これが役に立つことを願っています

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.