人非圣贤,孰能无过。程序也一样,关键是要学会“体面地犯错”
写程序的时候,你有没有遇到过这样的情况:
用户输入了文字,程序却要转换成数字 → 💥 崩溃
打开一个不存在的文件 → 💥 崩溃
除以0 → 💥 崩溃
列表索引超出范围 → 💥 崩溃
程序一崩溃,用户体验直接归零。
但其实,这些错误完全可以被优雅地处理——这就是异常处理的作用。
一、什么是异常?
异常 = 程序运行时发生的错误
当Python遇到无法继续执行的情况时,就会抛出一个异常。如果不处理,程序就会终止。
# 这几个都会引发异常int("hello")# ValueError10/0# ZeroDivisionErroropen("不存在.txt")# FileNotFoundError[1,2,3][10]# IndexError
常见异常类型(混个脸熟)
| 异常 | 含义 |
|---|
NameError | 变量未定义 |
TypeError | 类型错误(如字符串+数字) |
ValueError | 值错误(如int("abc")) |
ZeroDivisionError | 除以0 |
IndexError | 索引超出范围 |
KeyError | 字典键不存在 |
FileNotFoundError | 文件不存在 |
AttributeError | 对象没有这个属性 |
ImportError | 导入模块失败 |
二、异常处理的基本语法
1️⃣ try-except —— 捕获异常
try: num =int(input("请输入数字:")) print(f"你输入了:{num}")except ValueError: print("错误:请输入有效的数字!")
执行流程:
2️⃣ try-except-else —— 没有异常时执行
try: num =int(input("请输入数字:"))except ValueError: print("输入错误!")else: # 只有在没有异常时才执行 print(f"输入正确,数字是{num}")
3️⃣ try-except-finally —— 无论是否异常都执行
try: f =open("data.txt","r") data = f.read()except FileNotFoundError: print("文件不存在")finally:# 无论上面有没有异常,都会执行 print("这里的代码一定会执行")# 适合用来关闭资源 if 'f' in locals(): f.close()
finally 通常用于清理工作(关闭文件、释放连接等)
4️⃣ 捕获多个异常
try: num =int(input("请输入数字:")) result =100/ numexcept ValueError: print("请输入数字!")except ZeroDivisionError: print("不能除以0!")
5️⃣ 合并多个异常
try: num =int(input("请输入数字:")) result =100/ numexcept(ValueError, ZeroDivisionError)as e: print(f"输入错误:{e}")
6️⃣ 捕获所有异常(不推荐)
try: risky_code()except Exception as e: print(f"发生错误:{e}")
⚠️ 谨慎使用:捕获所有异常会隐藏你没预料到的bug。
三、手动抛出异常:raise
有时候,你想主动触发异常:
def set_age(age): if age <0or age >150: raise ValueError("年龄必须在0-150之间") print(f"年龄设置为:{age}")try: set_age(200)except ValueError as e: print(f"出错了:{e}")
重新抛出异常
try: do_something()except Exception as e: # 记录日志 log_error(e) # 重新抛出,让上层处理 raise
四、自定义异常
你可以定义自己的异常类型:
# 定义一个自定义异常class BalanceInsufficientError(Exception): """余额不足异常""" passdef withdraw(balance, amount): if amount > balance: raise BalanceInsufficientError(f"余额不足,需要{amount},只有{balance}") balance -= amount return balancetry: new_balance = withdraw(100,200)except BalanceInsufficientError as e: print(f"取款失败:{e}")
自定义异常让你的代码
意图更清晰,别人一看异常名就知道发生了什么。
五、assert 断言 —— 调试利器
断言:假设某个条件一定成立,如果不成立就抛出 AssertionError。
def divide(a, b): assert b !=0,"除数不能为0" return a / bdivide(10,0)# AssertionError: 除数不能为0
断言主要用于
开发阶段检测逻辑错误,生产环境可以用 -O 参数关闭。
六、实战案例
案例1:安全的整数输入函数
def safe_int_input(prompt, retry=3): """安全的整数输入函数,带重试机制""" for i inrange(retry): try: value =int(input(prompt)) return value except ValueError: print(f"输入无效,还剩{retry - i -1}次机会") raise ValueError(f"连续{retry}次输入错误")# 使用age = safe_int_input("请输入年龄:")print(f"年龄:{age}")
案例2:安全读取文件
def safe_read_file(filename, default=""): """安全读取文件,文件不存在时返回默认值""" try: with open(filename,"r", encoding="utf-8")as f: return f.read() except FileNotFoundError: print(f"文件 {filename} 不存在,返回默认值") return default except PermissionError:print(f"没有权限读取 {filename}") return default except Exception as e: print(f"读取文件时发生未知错误:{e}") return default content = safe_read_file("config.txt","{}")
案例3:带重试的网络请求模拟
import time import randomdef fetch_data(url): """模拟网络请求,随机失败""" if random.random()<0.7: # 70%概率失败 raise ConnectionError("网络连接失败") return f"从 {url} 获取的数据"def fetch_with_retry(url, max_retries=3, delay=1): """带重试的数据获取函数""" for attempt inrange(max_retries): try: print(f"尝试第 {attempt +1} 次...") data = fetch_data(url) print("成功!") return data except ConnectionError as e: print(f"失败:{e}") if attempt < max_retries -1: print(f"等待 {delay} 秒后重试...") time.sleep(delay) else: print("所有重试均失败") raise# 测试try: result = fetch_with_retry("https://api.example.com/data")except ConnectionError: print("获取数据失败,请稍后再试")
案例4:计算器(完整异常处理)
def calculator(): """带完整异常处理的计算器""" print("=== 简单计算器 ===") print("支持运算:+ - * /") while True: try: # 获取输入 exp =input("\n请输入表达式(如 3 + 5)或输入 q 退出:") if exp.lower()=='q': print("再见!") break # 解析表达式 parts = exp.split() if len(parts)!=3: print("格式错误,请使用:数字 运算符 数字") continue a, op, b = parts a =float(a) b =float(b) # 执行运算 if op =='+': result = a + b elif op =='-': result = a - b elif op =='*': result = a * b elif op =='/': if b ==0: raise ZeroDivisionError("不能除以0") result = a / b else: print(f"不支持的运算符:{op}") continue print(f"结果:{result}") except ValueError: print("错误:请输入有效的数字") except ZeroDivisionError as e: print(f"错误:{e}") except KeyboardInterrupt: print("\n用户中断")break except Exception as e: print(f"未知错误:{e}")calculator()
案例5:银行取款系统
class InsufficientBalanceError(Exception):"""自定义:余额不足异常"""pass class AccountNotFoundError(Exception):"""自定义:账户不存在异常"""pass class BankAccount: def __init__(self, account_id, balance=0): self.account_id = account_id self.balance = balance def withdraw(self, amount): if amount <=0: raise ValueError("取款金额必须大于0") if amount > self.balance: raise InsufficientBalanceError(f"余额不足!当前余额:{self.balance},需要:{amount}") self.balance -= amount return self.balance def deposit(self, amount): if amount <=0: raise ValueError("存款金额必须大于0") self.balance += amount return self.balance# 使用示例accounts ={"1001": BankAccount("1001",1000),"1002": BankAccount("1002",500),}def bank_system(): print("=== 银行系统 ===") try: acc_id =input("请输入账号:") if acc_id not in accounts: raise AccountNotFoundError(f"账号 {acc_id} 不存在") account = accounts[acc_id] action =input("请选择操作(1=存款,2=取款):") amount =float(input("请输入金额:")) if action =='1': new_balance = account.deposit(amount) print(f"存款成功!当前余额:{new_balance}") elif action =='2': new_balance = account.withdraw(amount) print(f"取款成功!当前余额:{new_balance}") else:print("无效操作") except AccountNotFoundError as e: print(f"账户错误:{e}") except InsufficientBalanceError as e: print(f"取款失败:{e}") except ValueError as e: print(f"金额错误:{e}") except Exception as e: print(f"系统错误:{e}")bank_system()
七、最佳实践与避坑指南
✅ 好的做法
# 1. 捕获具体异常,而不是全部try: result =int(text)except ValueError: # 具体 pass# 2. 异常信息要清晰raise ValueError(f"年龄不能为负数:{age}")# 3. 用 finally 释放资源f =open("file.txt")try: process(f)finally: f.close()# 4. 用 with 自动管理(更推荐)with open("file.txt")as f: process(f)# 5. 记录异常日志import loggingtry: risky_code()except Exception as e: logging.exception("发生异常")
❌ 坏的做法
# 1. 捕获所有异常(隐藏bug)try: data = process()except: # 连键盘中断都被吞了 pass# 2. 异常太宽泛except Exception as e:# 大多数情况没问题,但应该更具体 pass# 3. 什么都不做的 excepttry: x =int(text)except: pass# 静默失败,问题很难排查# 4. 抛出太通用的异常raise Exception("出错了")# 用更具体的异常类型
🔥 常见坑点
# 坑1: except 顺序try: value =int("abc")except Exception:# 这个会捕获所有,下面的 ValueError 永远不会执行 print("通用错误")except ValueError:# 永远到不了这里 print("值错误")# ✅ 正确:先具体后通用try: value =int("abc")except ValueError: print("值错误")except Exception: print("其他错误")# 坑2:不知道异常类型# 可以这样打印出来try: risky_code()except Exception as e: print(type(e).__name__, e)# 看看是什么异常
八、异常处理速查表
| 场景 | 写法 |
|---|
| 捕获特定异常 | except ValueError: |
| 捕获多个异常 | except (ValueError, TypeError): |
| 获取异常对象 | except ValueError as e: |
| 没有异常时执行 | else: |
| 无论是否异常都执行 | finally: |
| 手动抛出异常 | raise ValueError("信息") |
| 自定义异常 | class MyError(Exception): pass |
| 断言 | assert condition, "错误信息" |
写在最后
异常处理不是“逃避错误”,而是让程序在面对意外情况时,能够体面地处理。
记住这几个原则:
1️⃣ 预见可能出错的地方,提前用 try 保护
2️⃣ 捕获具体的异常,不要一股脑全抓
3️⃣ 异常信息要清晰,方便排查问题
4️⃣ finally 或 with 确保资源释放
5️⃣ 不要静默吞掉异常,至少打个日志
📌 如果觉得有用,点赞+在看+转发 给正在学Python的小伙伴!
优雅地犯错,体面地恢复。我们下期见! 👋