【问题标题】:Why is Python raising exception, despite a bare Except block?为什么 Python 会引发异常,尽管只有一个 except 块?
【发布时间】:2020-05-08 09:37:41
【问题描述】:

想象一下下面的代码:

def query(sql, data):
    with conn as cursor:
        try:
            cursor.execute(sql, data)
            rows = cursor.fetchall()
            conn.commit()
        except Exception as e:
            print(cursor._last_executed)
            print(e)

调用它时,err.InterfaceError("(0, '')") 从最后一行上升: print(e).

如果它是从print(cursor._last_executed) 升起的,我什至可以理解它,因为cursor 可能不可用或其他什么。但事实并非如此。

为什么,当我的裸异常应该处理所有事情时?

请忽略关于裸异常是好还是坏的讨论,这是另一个话题。问题是在这种情况下,异常是怎么产生的。

编辑:在繁重的数据库负载下,异常很少出现。您将无法复制它。

Edit2:我设法将回溯复制为哨兵报告中的文本:

InterfaceError: (0, '')
  File "run_signal_generator.py", line 39, in <module>
    main()
  File "run_signal_generator.py", line 35, in main
    ds.run_trades_stream()
  File "/home/deribit/rubber-band/data_stream/data_streamer.py", line 223, in run_trades_stream
    self.process_data(data)
  File "/home/deribit/rubber-band/data_stream/data_streamer.py", line 97, in process_data
    self.start_new_candle(timeframe)
  File "/home/deribit/rubber-band/data_stream/data_streamer.py", line 117, in start_new_candle
    self.notify()
  File "/home/deribit/rubber-band/data_stream/observer.py", line 13, in notify
    observer.update()
  File "/home/deribit/rubber-band/data_stream/observer.py", line 26, in update
    self.process_data()
  File "/home/deribit/rubber-band/data_stream/signal_generator.py", line 131, in process_data
    return self.process_potential_new_trade()
  File "/home/deribit/rubber-band/data_stream/signal_generator.py", line 160, in process_potential_new_trade
    return self.process_enter_signal()
  File "/home/deribit/rubber-band/data_stream/signal_generator.py", line 407, in process_enter_signal
    trade_id = self.store_enter_signal_db(data)
  File "/home/deribit/rubber-band/data_stream/signal_generator.py", line 522, in store_enter_signal_db
    return query(sql, db_data)["id"]
  File "/home/deribit/rubber-band/helpers/mysql.py", line 19, in query
    print(e)
  File "pymysql/connections.py", line 881, in __exit__
    self.commit()
  File "pymysql/connections.py", line 798, in commit
    self._execute_command(COMMAND.COM_QUERY, "COMMIT")
  File "pymysql/connections.py", line 1122, in _execute_command
    raise err.InterfaceError("(0, '')")

确实,它声称异常是从这条线引发的。

【问题讨论】:

  • 你的裸异常块正在处理一切。只是在处理它时,引发了另一个异常。这里的相关讨论是为什么异常在尝试打印时引发异常。
  • 你确定错误是在print(e)引发,而不是简单地打印那里?
  • 一个完整的追溯在这里会有所帮助...
  • 没有可用的通用回溯。我有一个哨兵回溯,我将其作为图像文件上传,并获得了反对意见并建议将其删除。根据哨兵回溯,错误确实是从这条线上上升的。我唯一的解释是哨兵回溯是错误的,它是从上面的行升起的。
  • 好的,我找到了原因 - 异常确实是从print 行引起的,如果您好奇,可以查看我的答案。

标签: python mysql exception error-handling


【解决方案1】:

似乎类似于this old question:mysql 连接和游标不是线程安全的,因此如果您在多个线程之间共享连接(而不是每个线程有一个连接),它们的状态基本上随时可能中断。

有可能由于某种原因__str__ on MySQL 的连接错误 ping 服务器以尝试获取更多信息,如果例如另一个线程已经改变了连接状态。 cursor._last_executed 可能没有这个问题,因为只是在执行请求或响应返回时将信息保存在本地。

正如其他评论者指出的那样,如果您遇到异常提供整个回溯和错误消息而不是一小部分信息。如果您需要匿名部分回溯,请这样做,但需要的 ESP 或假设越少越好。

【讨论】:

  • 我知道这可能是由于多个线程造成的,这里不是这样。它发生在单个线程中,在非常重的数据库负载下。我想,发生了连接超时之类的事情,所以我可以理解为什么会发生错误。我不明白为什么它会上升。如果异常是从cursor._last_executed 行引发的,那是有道理的。但是从我的哨兵报告来看,它是从下面的行中升起的,print(e)。所以我在这一点上唯一的猜测是哨兵报告不准确,它确实来自cursor._last_executed
  • 感谢您的回复,我对此表示赞赏并点赞。最终我找到了原因,如果您好奇,可以查看我的答案。
  • 嗯,对,所以问题是事务处于失败状态,但由于它在上下文管理器中,并且您在没有清理的情况下抑制异常,上下文管理器试图提交处于无效状态的事务,结果崩溃了。它会“指向”打印,因为这是上下文管理器的最后一行,并且它实际上没有更好的行来显示(__exit__ 触发的异常的常见问题)。
【解决方案2】:

想通了。

这是怎么回事:

1) 主query 引发错误。

2) 已处理。

3) 由于query 在上下文管理器中,上下文管理器退出它的对象,例如连接实例。

4) 由于MySQL服务器已经消失,连接类的__exit__方法无法正常执行。

5) 由于无法正确执行,它会引发一个错误在上下文管理器范围内,在query 内的裸处理异常范围之外。

如果您尝试以下代码,您可以获得相同的结果:

class ContextManagerException(Exception):
    pass


class TryBlockException(Exception):
    pass


class A(object):
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, tb):
        raise ContextManagerException


with A() as a:
    try:
        raise TryBlockException
        pass
    except Exception as e:
        print(e)

如您所见,引发的异常确实是从上下文管理器引发的,并且确实是从print(e) 行引发的,因为它是从上下文管理器执行的最后一行。

【讨论】:

  • Wrt (4),可能不是“mysql服务器已经消失”,而是“事务处于失败状态”(这也触发了python异常),你唯一能做的对处于失败状态的事务执行的操作是 ROLLBACK。上下文管理器将设置为正常情况下的 COMMIT 或异常情况下的 ROLLBACK,但由于您已捕获异常并且没有重新引发它,它认为一切顺利并尝试提交,但失败了。它触发的异常(和错误消息)虽然很糟糕。
  • 更准确地说,(4) 将是连接超时 IMO。但无论如何,如果我真的坚持要处理异常,我可以将上下文管理器放在另一个尝试中...除了块,它会没事的。
  • 你真的不应该那样做。要么使用上下文管理器并让它处理事务生命周期,要么不使用并自己处理整个事情。特别是因为您已经手动提交而不是让上下文管理器这样做。请注意,您可以打印您的信息并仍然正确使用上下文管理器,如果您只是重新引发它(在您的异常处理程序中是一个裸露的raise)。
  • 对不起,我没听懂。处理 CM 错误有什么不好的地方以及它可能带来的不良后果?
  • 您正在重复工作,并将上下文管理器置于意外状态。这里上下文管理器的全部意义在于自动打开一个事务,然后在主体结束时提交或回滚它,具体取决于是否引发异常。
猜你喜欢
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
  • 2014-03-23
  • 2021-07-14
  • 1970-01-01
  • 1970-01-01
  • 2010-10-22
  • 1970-01-01
相关资源
最近更新 更多