cx_Oracle и обработка исключений - хорошие практики? - PullRequest
24 голосов
/ 19 сентября 2011

Я пытаюсь использовать 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()

Тем не менее, я не совсем уверен, какой лучший дизайн для обработки исключений здесь.

Во-первых, я создаю объект db внутри блока try, чтобы перехватывать любые ошибки соединения.

Однако, если он не может подключиться, то db не будет существовать дальше вниз - именно поэтому я установил db = None выше. Однако это хорошая практика?

В идеале мне нужно отлавливать ошибки при подключении, затем ошибки при выполнении операторов DDL и т. Д.

Является ли вложение исключений хорошей идеей? Или есть лучший способ иметь дело с зависимыми / каскадными исключениями, подобными этому?

Кроме того, есть некоторые части (например, сбои соединения), где я хотел бы, чтобы скрипт просто завершился - отсюда закомментированный sys.exit() вызов. Тем не менее, я слышал, что использование обработки исключений для управления потоком, как это плохая практика. Мысли?

Ответы [ 2 ]

30 голосов
/ 24 марта 2012

Однако, если он не может подключиться, то db не будет существовать дальше - именно поэтому я установил db = None выше. Однако это хорошая практика?

Нет, установка db = None не является лучшей практикой. Есть две возможности, либо подключение к базе данных будет работать, либо не будет.

  • Подключение к базе данных не работает:

    Поскольку возбужденное исключение было поймано, а не повторно поднято, вы продолжаете, пока не достигнете cursor = db.Cursor().

    db == None, поэтому возникнет исключение, похожее на TypeError: 'NoneType' object has no attribute 'Cursor'. Поскольку исключение, сгенерированное при сбое соединения с базой данных, уже обнаружено, причина сбоя замаскирована.

    Лично я бы всегда вызывал исключение при соединении, если вы не собираетесь повторить попытку в ближайшее время. Как вы ловите это зависит от вас; если ошибка не устранена, я пишу по электронной почте, чтобы сказать «иди и проверь базу данных».

  • Подключение к базе данных работает:

    Переменная db назначена в вашем блоке try:... except. Если метод connect работает, то db заменяется объектом подключения.

В любом случае начальное значение db никогда не используется.

Однако я слышал, что использование обработки исключений для управления потоком как это плохая практика.

В отличие от других языков Python использует обработку исключений для управления потоком. В конце моего ответа я связался с несколькими вопросами о переполнении стека и программистах, которые задают аналогичный вопрос. В каждом примере вы увидите слова «но в Python».

Это не значит, что вы должны идти за борт, но Python обычно использует мантру EAFP , «Проще просить прощения, чем разрешения». Три лучших примера голосования в Как я могу проверить, существует ли переменная? являются хорошими примерами того, как вы можете использовать управление потоком или нет.

Является ли вложение исключений хорошей идеей? Или есть лучший способ борьбы с зависимыми / каскадными исключениями вроде этого?

Нет ничего плохого во вложенных исключениях, еще раз, если вы делаете это разумно. Посмотрите на ваш код. Вы можете удалить все исключения и обернуть все это в блок try:... except. Если возникает исключение, тогда вы знаете, что это было, но немного сложнее отследить, что именно пошло не так.

Что же произойдет, если вы захотите сами написать по электронной почте о сбое cursor.execute? У вас должно быть исключение около 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 документация

Почему бы не использовать исключения в качестве регулярного потока управления?
Является ли обработка исключений python более эффективной, чем PHP и / или другие языки?
Аргументы за или против использования try catch в качестве логических операторов

0 голосов
/ 16 ноября 2016

Другое и, возможно, элегантное решение - использовать декоратор для функций вызова вашей базы данных. Декоратор дает возможность исправить ошибку и повторить вызов базы данных. Для устаревших соединений исправление состоит в том, чтобы повторно соединиться и повторно выполнить вызов. Вот декоратор, который работал для меня:

####### 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

Примечание. Если вы используете пулы соединений, методы внутреннего пула соединений, которые проверяют соединение и обновляют его, если оно устарело, также решат проблему.

...