如何在python中使用上下文管理器捕获异常

时间:2018-01-12 03:32:10

标签: python generator try-except contextmanager

我有以下方法来设置与DB的连接并最终将其拆除。功能看起来像这样

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 个答案:

答案 0 :(得分:1)

1,sorta。整个try / except需要另一级别的缩进。

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()中执行此操作,需要使用setup_sql()进行另一次try:block:

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,没有。一个例外是立即“返回”它会回滚,并且重新提升它会中止你的阻止。如果您希望上下文管理器完成,请捕获异常并处理它们而不重新提升。如果您需要在那里引发异常并希望上下文管理器完成其工作,请设置一个标志变量并在关闭上下文管理器后引发,或者以另一种方式重构代码以实现该目标。

答案 1 :(得分:0)

我相信您实际上可以通过在方法内部的 yield 语句周围放置 try / except / finally 来封装上下文管理器中的错误处理,例如:

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

我没有试过这个,但我发现 this comment on another SO question 链接到关于 @contextmanager 的文档,解释了它是如何工作的。