Python 异常处理:别让程序因为一个小错误就崩了 写程序最怕什么?不是 bug 修不完,而是程序跑着跑着突然崩了,日志里一堆红字,用户看到的是”服务器错误”。
异常处理就是干这个的——预料到可能会出错的地方,提前想好怎么兜底 。Python 的 try...except 就是给代码加一道防护网,出错了不至于整个程序炸掉。
基础结构:try...except 1 2 3 4 5 6 try : result = 1 / 0 except ZeroDivisionError as e: print (f"发生错误:{e} " )
try 里面放”可能出错的代码”,except 里面放”出错之后怎么办”。
为什么要有这东西? 如果没有 try...except,程序直接崩溃退出,用户一脸懵。有了它,程序可以继续跑,顶多那条数据处理失败了。
捕获多种异常 可使用多个 except 块处理不同类型的异常,或在一个 except 中捕获多个异常:
1 2 3 4 5 6 7 8 9 10 try : num = int (input ("请输入一个整数:" )) result = 10 / num except ValueError: print ("输入错误:请输入有效的整数" ) except ZeroDivisionError: print ("计算错误:除数不能为 0" ) except (TypeError, OverflowError): print ("其他类型错误" )
也可以这样写,一个except捕获多个异常
1 2 3 4 5 try : num = int (input ("请输入数字:" )) result = 10 / num except (ValueError, ZeroDivisionError) as e: print (f"出错了:{e} " )
捕获所有异常(不推荐) 使用 except Exception 可捕获所有非系统退出的异常(不包括 KeyboardInterrupt 等):
1 2 3 4 5 6 try : lst = [1 , 2 , 3 ] print (lst[10 ]) except Exception as e: print (f"发生未知错误:{e} " )
except Exception 能捕获几乎所有常见的异常,看起来省事,但有隐患 。
为什么不推荐一味地捕获所有异常?
把不该忽略的异常也吞掉了
隐藏了代码里的 bug,本来应该修的问题变成了”程序还在跑但逻辑不对”
什么时候可以捕获所有异常? 只在程序的最顶层(比如 main 函数)做统一兜底,记录日志后优雅退出:
1 2 3 4 5 6 def main (): try : run_app() except Exception as e: logger.error(f"程序崩溃:{e} " ) sys.exit(1 )
其他地方尽量捕获具体的异常类型。
完整语法:try-except-else-finally 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 try : pass except SomeError as e: pass except AnotherError as e: pass except Exception as e: pass else : pass finally : pass
else:没事的时候跑一下 else 块在 try 没有抛出任何异常的时候执行。
1 2 3 4 5 6 try : result = 10 / 2 except ZeroDivisionError: print ("除数不能为0" ) else : print (f"计算结果:{result} " )
为什么不用 else,直接把代码放 try 里? 因为放在 try 里的所有代码都在同一个”防护罩”里,如果把后续处理也放进去,后续代码出错了会被误解为除法的错误。用 else 能把这层区分清楚。
finally:无论如何都要做的事 finally 不管发没发生异常都会执行,通常用来释放资源——关闭文件、关闭数据库连接、释放锁。
1 2 3 4 5 6 7 8 9 file = None try : file = open ("data.txt" , "r" ) content = file.read() except FileNotFoundError: print ("文件不存在" ) finally : if file: file.close()
一个需要注意的地方: finally 里不要写 return,它会覆盖 try 里的返回值。
1 2 3 4 5 6 7 def test (): try : return 1 finally : return 2 test()
手动抛出异常(raise) 有时候你觉得某个条件不满足,想主动让程序报错,用 raise。
1 2 3 4 5 6 7 8 9 10 def check_age (age ): if age < 0 : raise ValueError(f"年龄不能为负数:{age} " ) print (f"年龄是:{age} " ) try : check_age(-5 ) except ValueError as e: print (f"捕获到错误:{e} " )
raise…from:异常链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class DataLoadError (Exception ): pass def load_config (): try : with open ("config.json" ) as f: return f.read() except FileNotFoundError as e: raise DataLoadError("配置文件加载失败" ) from e try : config = load_config() except DataLoadError as e: print (f"错误:{e} " ) print (f"原始原因:{e.__cause__} " )
raise ... from e 能把原始异常带过去,排查问题的时候知道根因是什么。
抛出自定义异常 除了 Python 内置异常(如 ValueError、TypeError),还可定义自定义异常类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class InvalidScoreError (Exception ): """分数无效异常(分数必须在 0-100 之间)""" pass def check_score (score ): if not (0 <= score <= 100 ): raise InvalidScoreError(f"无效分数:{score} (必须在 0-100 之间)" ) try : check_score(150 ) except InvalidScoreError as e: print (f"错误:{e} " )
自定义异常的好处是:调用你代码的人能精准地捕获和处理业务层面的错误,而不是去判断一堆 ValueError。
内置异常体系 异常层次结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 BaseException ├── SystemExit ├── KeyboardInterrupt ├── GeneratorExit └── Exception ├── StopIteration ├── ArithmeticError │ ├── ZeroDivisionError │ └── OverflowError ├── LookupError │ ├── IndexError │ └── KeyError ├── TypeError ├── ValueError ├── AttributeError ├── IOError / OSError │ └── FileNotFoundError └── ...
常用内置异常速查表
异常类型
触发场景
示例
ValueError
值类型正确但值无效
int("abc")
TypeError
操作类型不匹配
"2" + 3
IndexError
序列索引超出范围
[1,2,3][5]
KeyError
字典键不存在
{"a":1}["b"]
AttributeError
对象没有该属性
"str".unknown()
ZeroDivisionError
除数为0
1 / 0
FileNotFoundError
文件不存在
open("no.txt")
ImportError
导入模块失败
import nonexist
StopIteration
迭代器没有下一个元素
next(iter([]))
KeyboardInterrupt
用户按 Ctrl+C
-
捕获 KeyboardInterrupt(优雅退出) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 import timedef main (): try : while True : print ("运行中..." ) time.sleep(1 ) except KeyboardInterrupt: print ("\n收到退出信号,正在清理资源..." ) print ("程序已安全退出" ) if __name__ == "__main__" : main()
异常与上下文管理器 with 语句自动管理资源 Python 的 with 语句通过上下文管理器自动处理资源的获取和释放,即使发生异常也会自动清理。
1 2 3 4 5 6 7 8 9 10 try : with open ("data.txt" , "r" ) as file: content = file.read() number = int (content) except FileNotFoundError: print ("文件不存在" ) except ValueError: print ("文件内容不是数字" )
自定义上下文管理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class DatabaseConnection : def __init__ (self, db_name ): self .db_name = db_name def __enter__ (self ): print (f"连接到数据库 {self.db_name} " ) self .conn = f"connection_{self.db_name} " return self .conn def __exit__ (self, exc_type, exc_val, exc_tb ): print (f"关闭数据库连接 {self.conn} " ) if exc_type: print (f"发生异常:{exc_val} " ) return False try : with DatabaseConnection("test" ) as conn: print (f"使用 {conn} 执行查询" ) raise ValueError("查询错误" ) except ValueError: print ("外层捕获异常" )