web-dev-qa-db-ja.com

cx_Oracleと例外処理-良い習慣ですか?

Cx_Oracleを使用してOracleインスタンスに接続し、DDLステートメントを実行しようとしています。

_db = None
try:
    db = cx_Oracle.connect('username', 'password', 'hostname:port/SERVICENAME')
#print(db.version)
except cx_Oracle.DatabaseError as e:
    error, = e.args
    if error.code == 1017:
        print('Please check your credentials.')
        # sys.exit()?
    else:
        print('Database connection error: %s'.format(e))
cursor = db.cursor()
try:
    cursor.execute(ddl_statements)
except cx_Oracle.DatabaseError as e:
    error, = e.args
    if error.code == 955:
        print('Table already exists')
    if error.code == 1031:
        print("Insufficient privileges - are you sure you're using the owner account?")
    print(error.code)
    print(error.message)
    print(error.context)
cursor.close()
db.commit()
db.close()
_

ただし、ここで例外処理に最適な設計が何であるかはよくわかりません。

まず、接続エラーをキャッチするために、tryブロック内にdbオブジェクトを作成します。

ただし、接続できない場合、dbはそれ以上下に存在しません。そのため、上記の_db = None_を設定します。しかし、それは良い習慣ですか?

理想的には、接続でエラーをキャッチし、次にDDLステートメントの実行でエラーをキャッチする必要があります。

入れ子の例外は良い考えですか?または、このような依存/カスケード例外を処理するより良い方法はありますか?

また、スクリプトをただ終了させたい部分(接続障害など)があります-したがって、コメントアウトされたsys.exit()呼び出し。ただし、このようなフロー制御に例外処理を使用するのは悪い習慣だと聞いたことがあります。考え?

24
victorhooi

ただし、接続できない場合、dbはそれ以上下に存在しません。そのため、上記の_db = None_を設定します。しかし、それは良い習慣ですか?

いいえ、_db = None_の設定はベストプラクティスではありません。データベースへの接続が機能するか、機能しないかの2つの可能性があります。

  • データベースへの接続が機能しません:

    発生した例外はキャッチされ、再発生しないため、cursor = db.Cursor()に達するまで続行します。

    _db == None_、したがって、_TypeError: 'NoneType' object has no attribute 'Cursor'_に似た例外が発生します。データベース接続が失敗したときに生成された例外はすでにキャッチされているため、失敗の理由は偽装されています。

    個人的には、すぐに再試行しない限り、常に接続例外を発生させます。どのようにキャッチするかはあなた次第です。エラーが解決しない場合は、「データベースを確認してください」というメールを送信します。

  • データベースへの接続は機能します:

    変数dbは_try:... except_ブロックで割り当てられます。 connectメソッドが機能する場合、dbは接続オブジェクトに置き換えられます。

いずれにしても、dbの初期値は使用されません。

しかし、このようなフロー制御に例外処理を使用するのは悪い習慣だと聞いたことがあります。

他の言語とは異なり、Pythondoesはフロー制御に例外処理を使用します。私の答えの最後にリンクしましたスタックオーバーフローに関するいくつかの質問と同様の質問をするプログラマーすべての例で、「Pythonで」という言葉が表示されます。

それはあなたが船外に行くべきだと言うことではありませんが、Pythonは一般的にマントラを使用します [〜#〜] eafp [〜#〜]「許可よりも許しを求める方が簡単です。」変数が存在するかどうかを確認するにはどうすればよいですか? で投票された上位3つの例は、その方法の良い例です。両方ともフロー制御を使用するかどうか。

入れ子の例外は良い考えですか?または、このような依存/カスケード例外を処理するより良い方法はありますか?

ネストされた例外には何も問題はありませんが、それを正常に実行する限り、再度です。コードを検討してください。すべての例外を削除し、_try:... except_ブロックで全体をラップできます。例外が発生した場合、それが何であるかはわかりますが、何が間違っていたかを正確に追跡するのは少し難しくなります。

_cursor.execute_の失敗について自分にメールを送信したい場合はどうなりますか?この1つのタスクを実行するには、_cursor.execute_の周りに例外が必要です。次に、外側の_try:..._でキャッチされるように例外を再発生します。再レイズしないと、何も起こらなかったようにコードが継続し、例外を処理するために外側の_try:..._に追加したロジックは無視されます。

最終的に、すべての例外は BaseException から継承されます。

また、スクリプトをただ終了させたい部分がいくつかあります(接続エラーなど)。したがって、コメントアウトされたsys.exit()呼び出しです。

単純なクラスとそれを呼び出す方法を追加しました。これはおおよそあなたがやろうとしていることをする方法です。これがバックグラウンドで実行される場合、エラーの印刷は価値がありません。エラーを手動で探してそこに座っている人はいません。標準的な方法でログインし、適切な人に通知する必要があります。この理由で印刷を削除し、ログのリマインダーに置き換えました。

connectメソッドが失敗し、例外が発生したときにクラスを複数の関数に分割したため、切断を試みた後、execute呼び出しは実行されず、スクリプトは終了します。

_import cx_Oracle

class Oracle(object):

    def connect(self, username, password, hostname, port, servicename):
        """ Connect to the database. """

        try:
            self.db = cx_Oracle.connect(username, password
                                , hostname + ':' + port + '/' + servicename)
        except cx_Oracle.DatabaseError as e:
            # Log error as appropriate
            raise

        # If the database connection succeeded create the cursor
        # we-re going to use.
        self.cursor = self.db.cursor()

    def disconnect(self):
        """
        Disconnect from the database. If this fails, for instance
        if the connection instance doesn't exist, ignore the exception.
        """

        try:
            self.cursor.close()
            self.db.close()
        except cx_Oracle.DatabaseError:
            pass

    def execute(self, sql, bindvars=None, commit=False):
        """
        Execute whatever SQL statements are passed to the method;
        commit if specified. Do not specify fetchall() in here as
        the SQL statement may not be a select.
        bindvars is a dictionary of variables you pass to execute.
        """

        try:
            self.cursor.execute(sql, bindvars)
        except cx_Oracle.DatabaseError as e:
            # Log error as appropriate
            raise

        # Only commit if it-s necessary.
        if commit:
            self.db.commit()
_

次に呼び出します:

_if __name__ == "__main__":

    Oracle = Oracle.connect('username', 'password', 'hostname'
                           , 'port', 'servicename')

    try:
        # No commit as you don-t need to commit DDL.
        Oracle.execute('ddl_statements')

    # Ensure that we always disconnect from the database to avoid
    # ORA-00018: Maximum number of sessions exceeded. 
    finally:
        Oracle.disconnect()
_

参考文献:

_cx_Oracle_ documentation

制御の通常のフローとして例外を使用しない理由
python PHPおよび/または他の言語?)よりも効率的な例外処理?
論理演算子としてtry catchを使用するかどうかの引数

31
Ben

別の、おそらくエレガントなソリューションは、データベース呼び出し関数にデコレーターを使用することです。デコレータを使用すると、エラーを修正して、データベース呼び出しを再試行できます。古い接続の場合、修復はコールを再接続して再発行することです。ここに私のために働いたデコレータがあります:

####### Decorator named dbReconnect ########
#Retry decorator
#Retries a database function twice when  the 1st fails on a stale connection
def dbReconnect():
    def real_decorator(function):
        def wrapper(*args, **kwargs):
            try:
                return function(*args, **kwargs)
            except  Exception as inst:
                print ("DB error({0}):".format(inst))
                print ("Reconnecting")
                #...Code for reconnection is to be placed here..
                ......
                #..end of code for reconnection
            return function(*args, **kwargs)
        return wrapper
    return real_decorator

###### Decorate the DB Call like this: #####
    @dbReconnect()
    def DB_FcnCall(...):
    ....

Githubの詳細: https://github.com/vvaradarajan/DecoratorForDBReconnect/wiki

注:接続プールを使用する場合、接続をチェックし、古い場合は更新する内部接続プール技術も問題を解決します。

1
giwyni