系列文章
Python 从入门到精通
第 11 / 12 篇
从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。
-
01
Python 怎么装、怎么用 -
02
变量、数字和字符串 -
03
列表:把一组数据放在一起 -
04
元组、集合和字典 -
05
条件判断:让程序做选择 -
06
循环:重复的事交给程序 -
07
函数:把代码整理成可复用的块 -
08
模块与包:拆分你的程序 -
09
输入、输出与字符串格式化 -
10
文件读写:保存程序的数据 -
11
异常处理:程序出错时怎么办正在阅读 -
12
基础阶段练习:命令行记账本
程序跑着跑着就报错了——这是常态,不是 bug 不 bug 的问题,是你准备好没有的问题。这一篇学异常处理:让程序出错时不崩溃,而是优雅地告诉你怎么了、该怎么办。
什么是异常
程序执行时遇到错误,Python 会”抛出一个异常”,没处理的话程序就崩了:
print(1 / 0)
# ZeroDivisionError: division by zero
# 程序在这里终止,后面的代码不执行
常见的异常类型(记住这几个,天天见):
ValueError:值不对,如int("abc")TypeError:类型不对,如"1" + 1FileNotFoundError:文件不存在KeyError:字典里没这个键IndexError:列表索引越界ZeroDivisionError:除零
try / except:兜住错误
try:
num = int(input("请输入一个数字:"))
print(f"它的两倍是 {num * 2}")
except ValueError:
print("这不是数字呀,请输入数字")
流程:先试 try 里的代码,如果抛出了 ValueError,就跳进 except ValueError 处理;没出错就正常走。程序不再崩溃。
捕获多种异常
try:
f = open("data.txt", "r")
num = int(f.read())
print(100 / num)
except FileNotFoundError:
print("文件不存在")
except ValueError:
print("文件内容不是数字")
except ZeroDivisionError:
print("文件里是 0,不能做除数")
一个 except 捕获多个类型,用元组:
except (ValueError, TypeError):
print("输入的类型或值不对")
用 except Exception 兜底所有异常——但别滥用,它会把 bug 也悄悄吞掉:
try:
result = 10 / user_input
except Exception: # 太宽了!连代码本身的 bug 也被吞了
result = 0
正确姿势:能精确到类型就精确,最后可以用 except Exception 兜底 + 打印错误信息。
拿到异常对象
try:
print(1 / 0)
except ZeroDivisionError as e:
print(f"出错了:{e}") # 出错了:division by zero
as e 把异常对象赋给变量,str(e) 能拿到错误描述,写日志时很有用。
else 和 finally
try 块还可以带两个附加块:
try:
num = int(input("请输入数字:"))
except ValueError:
print("输入不合法")
else:
print(f"输入合法,数字是 {num}") # 没异常才执行
finally:
print("无论成败都会执行") # 收尾:关文件、关连接
else:try 没抛异常才执行——把”正常逻辑”和”异常处理”分开,代码更清晰finally:不管有没有异常都执行——放清理动作(关闭文件、释放资源)
raise:主动抛异常
不光能接异常,还能主动抛。函数发现参数不合理,raise 出去让调用方处理:
def set_age(age):
if age < 0 or age > 150:
raise ValueError(f"年龄不合理:{age}")
print(f"年龄设为 {age}")
set_age(25) # 年龄设为 25
set_age(200) # ValueError: 年龄不合理:200
自定义异常类型(继承 Exception)在大型项目里很常见:
class BalanceNotEnough(Exception):
pass
def withdraw(balance, amount):
if amount > balance:
raise BalanceNotEnough("余额不足")
return balance - amount
异常处理的最佳实践
几条经验:
- 别吞异常:
except: pass是最坏的写法,出错了你什么都不知道。至少打日志或打印 - 精确捕获:能写具体类型就写具体类型,别一上来就 Exception
- 该崩就崩:有些错误不该处理(比如代码 bug),让它暴露出来反而好修
- 把异常当流程控制:用异常处理”用户输入错误”这类预期情况是可以的,但别用它替代正常逻辑判断
新手坑
坑 1:except 顺序。子类异常要写在父类前面,否则永远轮不到:
try:
int("abc")
except Exception: # 父类先匹配,下面的永远不执行
print("通用错误")
except ValueError:
print("值错误") # 永远打不出来!
坑 2:try 范围太大。把 20 行代码全塞 try 里,出错不知道是哪行。try 块越小越好。
坑 3:finally 里 return。finally 里的 return 会覆盖 try 里的 return,别这么写。
小结
- 异常是程序出错的信号,不处理就崩溃;
try/except接住它 - 捕获要精确:
except ValueError as e:,多类型用元组 else无异常执行,finally无论如何执行(放清理)raise主动抛异常,参数校验时很常用- 别吞异常,别用
except: pass,try 块越小越好
练习
- 写程序:让用户输入两个数字并相除,处理输入非数字和除零两种情况,都给出友好提示
- 写程序:读取一个文件,文件不存在时提示”文件不存在”而不是崩溃(两种方式:先判断 + try/except)
- 思考:为什么说
except: pass是危险写法?如果一段代码出错被静默吞掉,可能带来什么后果?