当前位置: 首页 > news >正文

13 - 异常处理

13 - 异常处理

程序不可能总是一帆风顺。文件可能不存在、网络可能断了、用户可能输入了奇怪的东西。异常处理就是教你的程序怎么"优雅地"应对这些意外。


什么是异常

你之前肯定已经见过报错了,比如:

print(1/0)# ZeroDivisionError: division by zeroprint(my_var)# NameError: name 'my_var' is not definedint("abc")# ValueError: invalid literal for int()

这些报错在 Python 里叫"异常"(Exception)。程序遇到异常就会停下来,如果不处理就直接崩了。


try-except

最基本的异常处理:

try:result=10/0exceptZeroDivisionError:print("不能除以零!")

try里面放可能出错的代码,except里面放出错后怎么应对。如果没出错,except就跳过。

捕获特定异常

try:num=int(input("输入一个数字:"))result=100/numprint(f"结果:{result}")exceptValueError:print("请输入有效的数字!")exceptZeroDivisionError:print("不能输入 0!")

可以有多个except,分别处理不同的异常。跟if-elif一样,匹配到第一个就执行,后面的跳过。

获取异常信息

try:result=10/0exceptZeroDivisionErrorase:print(f"出错了:{e}")# 出错了:division by zeroprint(f"异常类型:{type(e)}")

as e把异常对象存到变量e里,你可以查看具体信息。

捕获所有异常

try:# 一些可能出错的操作risky_operation()exceptExceptionase:print(f"出错了:{e}")

Exception是大多数异常的基类,能捕获除了KeyboardInterrupt(Ctrl+C)和SystemExit之外的所有异常。

不建议一开始就用except Exception,因为这样你什么异常都吞了,包括你没想到会发生的。最好是先捕获具体的异常,实在兜不住了再用Exception做最后一道防线。

try:process_data()exceptFileNotFoundError:print("文件不存在")exceptValueError:print("数据格式错误")exceptExceptionase:print(f"未知错误:{e}")# 最后的兜底

try-except-else-finally

完整形态:

try:f=open("data.txt","r")content=f.read()exceptFileNotFoundError:print("文件不存在")else:# try 成功(没异常)时执行print(f"读取了{len(content)}个字符")finally:# 不管有没有异常,都会执行print("操作结束")
  • else:try 没出错才执行。放在else里的好处是,如果else里的代码出了异常,不会被上面的except意外捕获。
  • finally:无论如何都执行。通常用来清理资源(关文件、关数据库连接等)。

不过说实话,else用得不多。finally如果你用了with语句也不需要了(with 自动处理清理工作)。


抛出异常

raise主动抛出异常:

defset_age(age):ifage<0:raiseValueError("年龄不能为负数")ifage>150:raiseValueError("年龄不太对吧")returnage set_age(-5)# ValueError: 年龄不能为负数

也可以把捕获的异常重新抛出去:

try:process_data()exceptValueErrorase:print(f"处理数据失败:{e}")raise# 重新抛出,让上层处理

自定义异常

你可以创建自己的异常类型:

classInsufficientBalanceError(Exception):"""余额不足异常"""def__init__(self,balance,amount):self.balance=balance self.amount=amountsuper().__init__(f"余额不足:当前{balance},需要{amount}")classBankAccount:def__init__(self,balance=0):self.balance=balancedefwithdraw(self,amount):ifamount>self.balance:raiseInsufficientBalanceError(self.balance,amount)self.balance-=amount# 使用account=BankAccount(100)try:account.withdraw(200)exceptInsufficientBalanceErrorase:print(f"取款失败:{e}")print(f"差额:{e.amount-e.balance}")

自定义异常的好处:调用者可以精确地捕获你的业务异常,而不会跟 Python 内置的异常搞混。


常见的异常类型

异常什么情况
ValueError值不对(如int("abc")
TypeError类型不对(如"a" + 1
KeyError字典的键不存在
IndexError列表索引越界
FileNotFoundError文件不存在
AttributeError对象没有这个属性/方法
ImportError模块导入失败
ZeroDivisionError除以零
NameError变量名不存在
PermissionError没有权限

你不用背这些,遇到了查一下就行。写多了自然就记住了。


异常处理的几个原则

不要吞掉异常

# 很糟糕的做法try:important_operation()exceptException:pass# 什么也不做,异常被默默吞掉了

这是最让人抓狂的代码之一。出了问题你完全不知道发生了什么,因为异常被静默忽略了。

至少打印个日志:

importloggingtry:important_operation()exceptExceptionase:logging.error(f"操作失败:{e}")raise# 或者重新抛出

精确捕获

# 不太好——什么都捕获了try:data=json.loads(text)exceptException:data={}# 更好——只捕获 JSON 解析错误try:data=json.loads(text)exceptjson.JSONDecodeError:data={}

捕获范围太广的话,可能会掩盖你没预料到的 bug。

EAFP vs LBYL

Python 社区有个哲学叫EAFP(Easier to Ask Forgiveness than Permission)——先干再说,出错了再处理。

# EAFP 风格(推荐)try:value=my_dict["key"]exceptKeyError:value="默认值"# LBYL 风格(Look Before You Leap)if"key"inmy_dict:value=my_dict["key"]else:value="默认值"

Python 更推崇 EAFP 风格。当然,如果已经有更好的 API(比如dict.get()),那就用它:

# 最佳value=my_dict.get("key","默认值")

异常链

Python 3 支持异常链,可以在捕获一个异常时抛出另一个异常,同时保留原始信息:

classConfigError(Exception):passtry:withopen("config.json")asf:config=json.load(f)exceptFileNotFoundErrorase:raiseConfigError("配置文件缺失")fromeexceptjson.JSONDecodeErrorase:raiseConfigError("配置文件格式错误")frome

这样报错信息里会同时显示原始异常和新的异常,方便追踪问题。


一个综合例子

importjsonimportloggingfrompathlibimportPath logging.basicConfig(level=logging.INFO)logger=logging.getLogger(__name__)defload_user_data(filepath:str)->dict:"""加载用户数据,处理各种可能的异常"""p=Path(filepath)try:content=p.read_text(encoding="utf-8")exceptFileNotFoundError:logger.warning(f"文件不存在:{filepath},返回空数据")return{}exceptPermissionError:logger.error(f"没有权限读取:{filepath}")raisetry:data=json.loads(content)exceptjson.JSONDecodeErrorase:logger.error(f"JSON 格式错误:{e}")return{}ifnotisinstance(data,dict):logger.warning("数据格式不对,应该是字典")return{}logger.info(f"成功加载数据,共{len(data)}个字段")returndata

这个例子展示了:

  • 分别处理不同的异常
  • 有的异常返回默认值(文件不存在、格式错误)
  • 有的异常向上抛(权限错误,这种应该让调用者知道)
  • 用日志记录而不是 print

assert 语句

assert是一种"断言"——你确信某个条件一定为真,如果不是,说明代码有 bug。

defcalculate_discount(price,discount):assert0<=discount<=1,f"折扣必须在 0-1 之间,收到:{discount}"returnprice*(1-discount)print(calculate_discount(100,0.8))# 20.0# calculate_discount(100, 1.5) # AssertionError: 折扣必须在 0-1 之间,收到:1.5

assert等价于:

ifnotcondition:raiseAssertionError(message)

assert 的使用场景

适合:检查"不应该发生"的情况,帮助开发阶段发现 bug。

defget_status_text(code):mapping={200:"OK",404:"Not Found",500:"Error"}assertcodeinmapping,f"未知状态码:{code}"returnmapping[code]

不适合:检查用户输入或运行时可能出现的正常错误。

# 错误用法!不要用 assert 做输入校验deflogin(username,password):assertusername!="","用户名不能为空"# 不好!用 if + raise

为什么?因为 Python 可以用-O参数运行,这时候所有 assert 都会被跳过。如果你的程序逻辑依赖 assert,优化模式下就全废了。

在测试中常用

assert 在单元测试里用得最多(后面第 20 章会简单讲 pytest):

deftest_add():assertadd(1,2)==3assertadd(-1,1)==0assertadd(0,0)==0

warnings 模块

有时候你想提醒用户"这样用不太好",但又不想直接报错中断程序。这时候用warnings

importwarningsdefold_function():warnings.warn("这个函数将在 v2.0 移除,请用 new_function()",DeprecationWarning)return"result"result=old_function()# 程序不会中断,但会显示警告

常用警告类型

类型含义
UserWarning普通警告(默认)
DeprecationWarning即将废弃的功能
FutureWarning未来版本会改变的行为
RuntimeWarning运行时可疑行为

过滤警告

有些警告你不想看到(比如第三方库发出来的),可以过滤掉:

importwarnings# 忽略所有警告warnings.filterwarnings("ignore")# 只忽略特定类型的警告warnings.filterwarnings("ignore",category=DeprecationWarning)# 把警告变成错误(测试时有用,确保没有隐藏问题)warnings.filterwarnings("error")

实际开发中,warnings主要用于库的开发者通知用户"某个功能要废弃了"。普通业务代码里用得不多,但知道有这个东西,看到别人的代码里用就不会懵。


本章小结

  • try-except捕获异常,finally确保清理代码执行
  • 尽量捕获具体异常,别一上来就except Exception
  • raise主动抛出异常,可以自定义异常类
  • 不要吞掉异常,至少要记日志
  • Python 推崇 EAFP 风格:先做再处理异常
  • raise ... from e可以建立异常链
  • assert用于断言"不应该发生"的情况,不要用来做输入校验
  • warnings模块发出非致命警告,适合通知用户功能即将废弃

面试题

Q1:try-except-else-finally各自的执行条件是什么?

点击查看答案
  • try:始终执行
  • except:try 中发生对应异常时执行
  • else:try 中没有发生异常时执行
  • finally无论如何都执行(有异常没异常都执行)

执行顺序:try → (except 或 else) → finally

finally即使在 try/except 中有 return 语句也会执行(return 会等 finally 执行完再返回)。

Q2:raiseraise e有什么区别?

点击查看答案

在 except 块中:

  • raise(不带参数):重新抛出当前异常,保留完整的堆栈追踪
  • raise e:抛出异常对象 e,堆栈追踪会从这一行重新开始
try:1/0exceptZeroDivisionErrorase:raise# 堆栈指向 1/0 那一行(推荐)# raise e # 堆栈指向 raise e 这一行(丢失原始位置)

所以重新抛出时推荐用raise而不是raise e,这样更容易定位问题的真正来源。

Q3:什么是 EAFP?跟 LBYL 有什么区别?

点击查看答案
  • EAFP(Easier to Ask Forgiveness than Permission):先执行操作,出异常了再处理。Python 推荐的风格。
  • LBYL(Look Before You Leap):先检查条件,确认安全了再执行。
# EAFPtry:value=d[key]exceptKeyError:value=default# LBYLifkeyind:value=d[key]else:value=default

EAFP 的优点:代码更简洁,避免了"检查-执行"之间的竞态条件。缺点:异常频繁发生时性能较差(异常处理有开销)。

Q4:为什么要自定义异常?直接 raise ValueError 不行吗?

点击查看答案

自定义异常的好处:

  1. 语义清晰InsufficientBalanceErrorValueError更准确地描述了问题
  2. 精确捕获:调用者可以只捕获你的业务异常,不会误捕其他 ValueError
  3. 携带上下文:自定义异常可以包含业务相关的数据(如余额、差额)
  4. 异常层次:可以建立项目特有的异常层次结构
try:account.withdraw(200)exceptInsufficientBalanceError:# 只处理余额不足exceptValueError:# 处理其他值错误(金额不能为负等)

小项目用内置异常也够了,大项目建议定义自己的异常体系。


http://www.jsqmd.com/news/900080/

相关文章:

  • 从零到一:MobileNet V1/V2 核心架构解析与轻量级模型实战搭建
  • 告别自签名警告:为Proxmox VE管理界面配置域名与SSL证书
  • LoongSon——PMON实战命令手册:从启动到调试
  • 2026年Q2云南厨电工厂深度解析:家园优品如何引领区域产业升级? - 2026年企业资讯
  • 3分钟学会Windows 11终极优化:Win11Debloat免费系统清理完整指南
  • 告别手写定位符!用 Appium Inspector 的录制和搜索功能快速生成 Python/Java 测试脚本
  • 68_《智能体微服务架构企业级实战教程》运维与部署之编写docker-compose部署脚本
  • LeagueAkari:英雄联盟玩家的智能效率革命,告别传统低效操作
  • 2026年Q2苏州的经济合同纠纷法律服务深度解析与选择指南 - 2026年企业资讯
  • 从Linux到SPDK:NVMe Namespace的创建、绑定与高性能存储实践
  • SAP FICO 集成场景下GL_ACCT_MASTER_SAVE的实战应用与BAPI封装
  • AI 基础概念卡片
  • ChatGPT客服话术设计全链路拆解,从客户投诉归因→话术颗粒度分级→AB测试验证→实时迭代机制
  • 工期紧张时的救星:哪些HC-276厂商能做到灵活排产并按时交付? - 品牌2025
  • Cortex-R4处理器nCPUHALT信号原理与应用解析
  • Pearcleaner:Mac应用清理的终极解决方案,彻底释放存储空间
  • Notepad++ 详细下载安装全流程指南
  • 2026年 热电阻/铠装热电阻/温度传感器厂家推荐榜:TKWZPK-24-440/WZPK-24-440型号精度与耐用性深度解析 - 品牌企业推荐师(官方)
  • 边缘计算安全最佳实践:保护边缘环境中的数据和应用
  • 第06篇|module.json5 深读:设备类型、权限、Ability 与智能体配置
  • 【Qt】QModbusRtuSerialMaster:串行Modbus客户端实战与帧时序调优
  • 被低估的超级不锈钢:为什么高端装备都在悄悄使用UNS S21800? - 品牌2025
  • Go语言timer源码:时间调度实现深度解析
  • 航空发动机叶盘系统的多场耦合振动特性及优化设计【附程序】
  • Adobe-GenP 3.0完整指南:如何免费解锁Adobe Creative Cloud全系列软件
  • 酒店门锁V10SDK接口vb模块-幽冥大陆(一百27)—东方仙盟
  • AI原生网站构建:智能体与MCP工具协同架构实战
  • 蓝牙协议栈探秘:从HCI到AMP的协同架构
  • 实战解析:基于MapReduce的气象数据清洗与质量控制
  • LeetCode 102:二叉树的层序遍历 | BFS