简体   繁体   English

python中的上下文管理器如何捕获异常

[英]How are exceptions caught with context manager in python

I have the following method to setup a connection with the DB and tear it down in the end.我有以下方法来建立与数据库的连接并最终将其拆除。 Function looks something like this函数看起来像这样

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

Now if I had to replace this with context manager, I think it would look something like现在,如果我不得不用上下文管理器替换它,我认为它看起来像

@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

Now my questions are现在我的问题是

  1. Is the above interpretation of context manager correct?上下文管理器的上述解释是否正确?
  2. If there was an error that occurred while doing pymysql.connect(...) statement inside contextmanager , how would that be handled?如果在contextmanager执行pymysql.connect(...)语句时发生错误,将如何处理? How would it be bubbled up to the calling function?它将如何冒泡到调用函数?
  3. What happens if there is an exception in doStuffGeneratingException() in the with implementation?如果with实现中的doStuffGeneratingException()出现异常会发生什么? Will the control go first to the setup_sql to execute the statements after yield ?控件会先到setup_sql执行yield之后的语句吗?

1, sorta. 1,有点。 The entire try/except needs another level of indentation.整个 try/except 需要另一个级别的缩进。

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

        except:
             cur.rollback()
             raise

2, An error there isn't handled anywhere in your code, so python itself would report on the exception and halt execution. 2、代码中没有处理任何地方的错误,因此python本身会报告异常并停止执行。 It could be caught anywhere you chose.它可以在您选择的任何地方捕获。 Inside setup_sql() and read_db() are both viable, but typically you want to handle exceptions as close to what's raising them as possible if you intend on doing something about it.在 setup_sql() 和 read_db() 内部都是可行的,但是如果您打算对此做些什么,通常您希望尽可能接近引发它们的异常处理。 To do it inside read_db() another try: block would be needed around your with setup_sql():要在 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, no. 3,没有。 An exception is an immediate 'return' it will hit your rollback, and re-raising it will abort your with block.一个例外是立即“返回”,它将击中您的回滚,而重新引发它将中止您的 with 块。 If you want the context manager to complete, catch the exceptions and deal with them without re-raising.如果您希望上下文管理器完成,请捕获异常并处理它们而无需重新引发。 If you need to raise an exception there and want the context manager to finish its work, set a flag variable and raise after closure of the context manager or refactor your code another way to achieve that objective.如果您需要在那里引发异常并希望上下文管理器完成其工作,请设置一个标志变量并在上下文管理器关闭后引发或以另一种方式重构您的代码以实现该目标。

I believe you actually can encapsulate the error handling in the context manager by placing try / except / finally around the yield statement inside of the method, like:我相信您实际上可以通过在方法内部的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()

I haven't tried this, but I had come across this comment on another SO question that links to the documentation about @contextmanager explaining how this works.我还没有尝试过这个,但是我在另一个 SO 问题上遇到了这个评论,该问题链接到关于@contextmanager的文档,解释了它是如何工作的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM