Как ловятся исключения с помощью менеджера контекста в python

У меня есть следующий метод, чтобы установить соединение с БД и разорвать его в конце. Функция выглядит примерно так

def read_db(self, sql_statement):

    conn = pymysql.connect(host=self.h,user=self.u,passwd=self.pw,
                           db=self.db,port=self.p)
    try:
        with conn.cursor() as cur:
            cur.execute(sql_statement)
            doStuffGeneratingException()

    except Exception:
        cur.rollback()
        raise

    finally:
        conn.close()   

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

@contextmanager
def setup_sql():
    conn = pymysql.connect(host=self.h,user=self.u,passwd=self.pw,
                           db=self.db,port=self.p)
    yield conn.cursor()
    connection.cursor().close()
    self.connection.close()


def read_db(self, sql_statement):
    with setup_sql() as cur:
    try:
        cur.execute(sql_statement)
        doStuffGeneratingException()

    except:
         cur.rollback()
         raise

Теперь мои вопросы

  1. Является ли приведенная выше интерпретация контекстного менеджера правильной?
  2. Если во время выполнения произошла ошибка pymysql.connect(...) заявление внутри contextmanagerКак это будет обрабатываться? Как бы это было связано с вызывающей функцией?
  3. Что произойдет, если есть исключение в doStuffGeneratingException() в with реализация? Будет ли управление идти первым к setup_sql выполнять заявления после yield?

2 ответа

1, Сорта. Вся попытка / исключение требует другого уровня отступа.

def read_db(self, sql_statement):
    with setup_sql() as cur:
        try:
            cur.execute(sql_statement)
            doStuffGeneratingException()

        except:
             cur.rollback()
             raise

2. Ошибка там нигде не обрабатывается в вашем коде, поэтому сам Python сообщит об исключении и остановит выполнение. Его можно поймать где угодно. Внутри setup_sql() и read_db() являются жизнеспособными, но обычно вы хотите обрабатывать исключения как можно ближе к тому, что их вызывает, если вы собираетесь что-то с этим делать. Чтобы сделать это внутри read_db(), потребуется еще один блок try: вокруг с помощью setup_sql():

def read_db(self, sql_statement):
    try:
        with setup_sql() as cur:
            try:
                cur.execute(sql_statement)
                doStuffGeneratingException()

            except:
                 # gets exceptions thrown by cur.execute() and doStuffGeneratingException() 
                 # will not process context manager statements after yield if flow doesn't continue in this function past this except block
                 cur.rollback()
                 raise
    except:
        # gets exceptions thrown by `with setup_sql() as cur:`
        # again none of the statements within the context manager after the statement that raises an exception will be executed
        pass

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

Я полагаю, что вы действительно можете инкапсулировать обработку ошибок в менеджере контекста, поместив try/except/finally вокруг yieldоператор внутри метода, например:

      from contextlib import contextmanager

@contextmanager
def setup_sql():
    conn = pymysql.connect(host=self.h,user=self.u,passwd=self.pw,
                           db=self.db,port=self.p)
    cursor = conn.cursor()
    try:
        yield cursor
    except Exception:
        cursor.rollback()
        raise
    finally:
        cursor.close()
        conn.close()

def read_db(self, sql_statement):
    with setup_sql() as cur:
        cur.execute(sql_statement)
        doStuffGeneratingException()

Я не пробовал это, но я наткнулся на этот комментарий к другому вопросу SO , который ссылается на документацию о @contextmanager, объясняющую, как это работает.

Другие вопросы по тегам