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

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

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

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

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

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

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


person victorhooi    schedule 19.09.2011    source источник
comment
Я сомневаюсь, что вам нужно обрабатывать отдельные коды ошибок только для их печати, исключение должно иметь сообщения от драйвера. Вы можете использовать вложенное исключение, например. если вы делаете отказоустойчивый код, например, запрос запроса -> ошибка -> повторное подключение -> повторная попытка. Ваш поток кода выглядит нормально для меня. Хорошей идеей может быть создание функции и добавление кода очистки в блок finally (закрыть базу данных курсора и т. д.).   -  person sherpya    schedule 08.02.2012


Ответы (2)


Однако, если он не может подключиться, то 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 в качестве логических операторов

person Ben    schedule 24.03.2012
comment
Я использую фрагмент кода, который вы предложили Бену, но я сталкиваюсь со следующей ошибкой ORA-24550: получен сигнал: необработанное исключение: код = c0000005 Flags = 0 Не удается поймать исключение при неправильном вводе хоста или SID и моем Сервер Python Django падает - person sarath joseph; 07.08.2014
comment
Я предполагаю, что это ваш вопрос @sarath. Это проблема Oracle, она не имеет ничего общего с Python, и я не понимаю, насколько актуален ваш сервер Django, если вы не говорите, что cx_Oracle.DatabaseError не перехватывает исключение. Можете ли вы добавить больше деталей к вашему вопросу? - person Ben; 08.08.2014
comment
Таким образом, оператор cx_Oracle.connect не перехватывается моими блоками исключений на неправильном хосте и записи SID. Как мне поймать исключение, которое я использую cx.Oracle.connect в блоке try с блоками исключений, которые следуют за исключением cx_Oracle.DatabaseError as e: error, = e.args if error.code == 1017: print('Пожалуйста проверьте свои учетные данные.') else: print('Ошибка подключения к базе данных: %s' % (e,)) поднять кроме cx_Oracle.Error as e: error=e.args print('Ошибка.') поднять - person sarath joseph; 08.08.2014
comment
Я не смогу ответить на вопрос в комментариях @sarath, вам нужно задать его (и если вы продолжите их удалять, вы получите бан, так что не делайте этого). Просто отредактируйте свой предыдущий вопрос со всей информацией. На самом деле вы не опубликовали точный код, который вы используете, или точное сообщение об ошибке, которое вы получаете от Python. Не ловите исключение, дайте ему подняться и посмотрите, что это такое. - person Ben; 08.08.2014
comment
connect не должно быть внутри блока try. - person Beau Barker; 12.01.2018
comment
В __main__ @Beau? Спасибо, я переместил его. Я также удалил print, который раздражал меня в течение 6 лет. - person Ben; 12.01.2018
comment
Да, намного лучше @Ben ???? - person Beau Barker; 13.01.2018
comment
Как можно красиво восстановить соединение в бесконечном цикле и продолжать попытки до тех пор, пока он не соединится? - person Superdooperhero; 10.06.2020
comment
Поместите .connect() в блок try: except и игнорируйте все ошибки @Superdooperhero; try: cx_Oracle.connect(...) except cx_Oracle.DatabaseError: pass. Я бы настоятельно рекомендовал против этого, хотя. Вы никогда не узнаете, если что-то было не так. - person Ben; 10.06.2020

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

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

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

person giwyni    schedule 16.11.2016