Python异常机制:try-except-finally 核心语法与执行流程
异常处理,说白了就是给代码买份保险。
我在做市商系统的时候,最怕的就是半夜被报警电话吵醒。后来我学乖了——所有可能出问题的地方,都得套上 try-except。你想想看,行情数据突然断流、交易所接口超时、数据库连接池耗尽……这些在量化交易里都是家常便饭。没有异常处理,程序一崩就是几百万的损失。
try-except-finally 的基本结构
Python 的异常机制其实很简单。核心就三个关键字:try、except、finally。
try:
# 可能出问题的代码
result = 10 / 0
except ZeroDivisionError:
# 出问题后的补救措施
print("除数不能为零!")
finally:
# 不管出不出问题,都会执行
print("这段代码一定会跑")
嗯,这里要注意:finally 不是必须的。但我在市商系统里,每个 try 块后面几乎都跟着 finally。为什么?因为要释放资源啊!
核心执行流程:
- 先执行 try 块里的代码
- 如果没异常:跳过 except,直接进 finally
- 如果出异常:立即跳到对应的 except 块,处理完再进 finally
- 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 # 统计操作次数
这个模板我用了好几年,效果不错。核心思路就三点:
- 网络类异常:自动重试,指数退避
- 业务类异常:立即停止,通知人工
- 未知异常:记录日志,向上传播
嗯,异常处理这东西,说白了就是「防患于未然」。你永远不知道生产环境会出什么幺蛾子,但有了这套机制,至少程序不会轻易崩溃。我见过太多因为没做异常处理,导致半夜爬起来手动修复的案例了……
记住一句话:好的异常处理,是量化系统稳定运行的基石。