系列文章

Python 从入门到精通

第 11 / 12 篇

从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。

  1. 01
    Python 怎么装、怎么用
  2. 02
    变量、数字和字符串
  3. 03
    列表:把一组数据放在一起
  4. 04
    元组、集合和字典
  5. 05
    条件判断:让程序做选择
  6. 06
    循环:重复的事交给程序
  7. 07
    函数:把代码整理成可复用的块
  8. 08
    模块与包:拆分你的程序
  9. 09
    输入、输出与字符串格式化
  10. 10
    文件读写:保存程序的数据
  11. 11
    异常处理:程序出错时怎么办正在阅读
  12. 12
    基础阶段练习:命令行记账本

查看整个系列 →

程序跑着跑着就报错了——这是常态,不是 bug 不 bug 的问题,是你准备好没有的问题。这一篇学异常处理:让程序出错时不崩溃,而是优雅地告诉你怎么了、该怎么办。

什么是异常

程序执行时遇到错误,Python 会”抛出一个异常”,没处理的话程序就崩了:

print(1 / 0)
# ZeroDivisionError: division by zero
# 程序在这里终止,后面的代码不执行

常见的异常类型(记住这几个,天天见):

  • ValueError:值不对,如 int("abc")
  • TypeError:类型不对,如 "1" + 1
  • FileNotFoundError:文件不存在
  • 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 块越小越好

练习

  1. 写程序:让用户输入两个数字并相除,处理输入非数字和除零两种情况,都给出友好提示
  2. 写程序:读取一个文件,文件不存在时提示”文件不存在”而不是崩溃(两种方式:先判断 + try/except)
  3. 思考:为什么说 except: pass 是危险写法?如果一段代码出错被静默吞掉,可能带来什么后果?