Python异常机制:try-except-finally 核心语法与执行流程

异常处理,说白了就是给代码买份保险。

我在做市商系统的时候,最怕的就是半夜被报警电话吵醒。后来我学乖了——所有可能出问题的地方,都得套上 try-except。你想想看,行情数据突然断流、交易所接口超时、数据库连接池耗尽……这些在量化交易里都是家常便饭。没有异常处理,程序一崩就是几百万的损失。

try-except-finally 的基本结构

Python 的异常机制其实很简单。核心就三个关键字:tryexceptfinally

try:
    # 可能出问题的代码
    result = 10 / 0
except ZeroDivisionError:
    # 出问题后的补救措施
    print("除数不能为零!")
finally:
    # 不管出不出问题,都会执行
    print("这段代码一定会跑")

嗯,这里要注意:finally 不是必须的。但我在市商系统里,每个 try 块后面几乎都跟着 finally。为什么?因为要释放资源啊!

核心执行流程:

  1. 先执行 try 块里的代码
  2. 如果没异常:跳过 except,直接进 finally
  3. 如果出异常:立即跳到对应的 except 块,处理完再进 finally
  4. finally 里的代码,无论如何都会执行

多级异常捕获:别一把抓

我见过很多新手写代码,一个 except Exception 就把所有异常全吞了。这其实很危险。

举个例子,你在市商系统里做订单管理:

try:
    order_id = exchange.submit_order(symbol, side, quantity, price)
    self.order_book[order_id] = order
    self.update_position(order_id)
except ConnectionError:
    # 网络断了,重连
    self.reconnect_exchange()
except TimeoutError:
    # 超时了,查一下订单状态
    self.query_order_status(order_id)
except InsufficientBalance:
    # 余额不足,停止交易
    self.stop_trading()
    self.notify_admin()
except Exception as e:
    # 其他未知错误,记录日志
    self.logger.critical(f"未知错误: {e}", exc_info=True)

你看,不同的异常要有不同的处理方式。我曾经因为把所有异常都写成 except Exception,结果网络断了之后程序还在傻傻地发单,最后导致重复下单。那教训,啧啧……

我的个人习惯:

  • 先捕获具体的异常类型
  • 最后留一个 except Exception 兜底
  • 兜底的一定要记录完整堆栈(exc_info=True

else 子句:没异常时做点啥

很多人不知道,try-except 后面还能跟 else。它的作用是:只有没发生异常时,才执行 else 里的代码

try:
    price_data = fetch_market_data(symbol)
except ConnectionError:
    price_data = self.cache.get(symbol)  # 用缓存数据
else:
    self.cache.update(symbol, price_data)  # 更新缓存
finally:
    self.last_fetch_time = time.time()

为什么要用 else?直接把更新缓存的代码写在 try 里不行吗?

行,但不好。你想想看,如果更新缓存本身也出错了,那异常会被 try 块捕获,导致你误以为是获取数据出了问题。else 能把「正常流程」和「异常处理」彻底分开,代码更清晰,调试也更容易。

finally 的妙用:资源清理

在市商系统里,finally 最常见的用途就是释放资源。比如文件句柄、数据库连接、网络 socket 等等。

db_conn = None
try:
    db_conn = create_db_connection()
    db_conn.execute("UPDATE positions SET ...")
    db_conn.commit()
except DatabaseError:
    db_conn.rollback()
    self.logger.error("数据库更新失败,已回滚")
finally:
    if db_conn:
        db_conn.close()  # 确保连接被关闭

这里有个坑:finally 里如果也抛异常,会覆盖掉之前的异常。我曾经就踩过这个坑——try 里报错了,finally 里关闭连接时又报错,结果程序只打印了 finally 的异常,真正的错误原因反而丢了。

避坑指南:

  • finally 里尽量只做简单操作
  • 如果必须做复杂操作,用 try-except 再包一层
  • 或者用 contextlib.suppress 忽略 finally 里的异常

实战:市商系统的异常处理模板

最后,分享一个我在市商系统里常用的异常处理模板。说白了,就是一套标准流程:

def safe_market_operation(operation_func, retry_count=3):
    """
    带重试机制的市商操作模板
    """
    for attempt in range(retry_count):
        try:
            result = operation_func()
            return result
        except (ConnectionError, TimeoutError) as e:
            if attempt == retry_count - 1:
                raise  # 最后一次重试还失败,向上抛
            self.logger.warning(f"第{attempt+1}次重试: {e}")
            time.sleep(2 ** attempt)  # 指数退避
        except InsufficientBalance:
            self.stop_trading()
            self.notify_admin()
            raise
        except Exception as e:
            self.logger.critical(f"未知错误: {e}", exc_info=True)
            raise
        finally:
            self.operation_count += 1  # 统计操作次数

这个模板我用了好几年,效果不错。核心思路就三点:

  • 网络类异常:自动重试,指数退避
  • 业务类异常:立即停止,通知人工
  • 未知异常:记录日志,向上传播

嗯,异常处理这东西,说白了就是「防患于未然」。你永远不知道生产环境会出什么幺蛾子,但有了这套机制,至少程序不会轻易崩溃。我见过太多因为没做异常处理,导致半夜爬起来手动修复的案例了……

记住一句话:好的异常处理,是量化系统稳定运行的基石