【问题标题】:How are exceptions caught with context manager in pythonpython中的上下文管理器如何捕获异常
【发布时间】:2018-01-12 03:32:09
【问题描述】:

我有以下方法来建立与数据库的连接并最终将其拆除。函数看起来像这样

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. 如果在contextmanager 中执行pymysql.connect(...) 语句时发生错误,将如何处理?它将如何冒泡到调用函数?
  3. 如果doStuffGeneratingException()with 实现中出现异常会怎样?控件会先到setup_sql 执行yield 之后的语句吗?

【问题讨论】:

    标签: python generator try-except contextmanager


    【解决方案1】:

    1,有点。整个 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() 块:

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

    【讨论】:

      【解决方案2】:

      我相信您实际上可以通过在方法内部的 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 的文档,解释了它是如何工作的。

      【讨论】:

        猜你喜欢
        • 2023-03-26
        • 2023-03-23
        • 2015-05-29
        • 1970-01-01
        • 1970-01-01
        • 2021-11-20
        • 1970-01-01
        • 2014-11-30
        相关资源
        最近更新 更多