昨晚有个读者私信我:"我写的代码在自己电脑上跑得好好的,发给同事用就报错,怎么办?"
我让他把报错截图发过来一看——典型的"用户输入不合法"导致程序崩溃。他写的代码,大概长这样:
age = input("请输入你的年龄:")age = int(age)print(f"你今年{age}岁")
他自己测试,永远输入"25",代码没问题。但同事不小心输入了"二十五",程序直接抛出ValueError崩掉了。
这就是今天要聊的话题——异常处理。从"能跑"到"稳跑",是每个编程新手必须跨过的坎。
一、为什么你的代码总是"看着没问题,一用就崩"
我总结了一下,新手写代码最容易崩的几个场景,基本就这几类:
1. 用户输入不规范:上面那个int()转换就是典型。用户可能输入字母、空格、甚至直接回车。
2. 文件不存在:你写open("data.txt")读文件,但用户没把这个文件放在指定位置。
3. 网络请求失败:爬虫或者调API时,网络抖一下、接口返回500,程序就抛异常。
4. 除数为0:计算平均分、分页时除以用户输入的数字,结果是个0。
5. 列表下标越界:你从列表里取第5个元素,但用户只给了3个。
这些场景有个共同点——不是你的代码逻辑错了,而是外部输入或环境变了。一个真正"稳"的程序,必须能优雅地处理这些意外。
二、try-except:让你的程序"会自救"
Python 提供了一个非常简单的机制——try-except,说白了就是"试试这段代码会怎么样,出了问题就按我的方案处理"。
最基础的用法:
try: age = int(input("请输入你的年龄:")) print(f"你今年{age}岁")except ValueError: print("输入有误,请输入数字")
这样,用户输入"二十五"的时候,程序会打印"输入有误,请输入数字",而不是直接崩掉。这就是异常处理的核心思想——把可能出错的代码包起来,给它一个兜底方案。
更完整的写法是 try-except-else-finally 四个一起用:
try: f = open("data.txt", "r", encoding="utf-8") content = f.read()except FileNotFoundError: print("文件不存在")else: print("读取成功,共", len(content), "个字符")finally: f.close() # 不管出没出错,文件都要关掉
四个块的作用:
- try:放可能出错的代码
- except:出错时执行,可以指定具体异常类型
- else:没出错时执行
- finally:不管出不出错都要执行(关文件、关数据库连接的场景必备)
三、5个新手最容易踩的坑,看完少走3个月弯路
坑1:except后面什么都不写
很多新手写except: pass,这样会让所有错误都被悄悄吞掉,bug难以发现。
# 错误写法 ❌try: do_something()except: pass# 正确写法 ✅try: do_something()except ValueError as e: print(f"参数错了:{e}")
坑2:把所有异常都用Exception一把抓
Exception 能抓住几乎所有异常,但这样会把你不想处理的错误(比如KeyboardInterrupt用户按Ctrl+C)也抓住,导致程序退不出。
建议只抓你预期的、想处理的异常类型。比如读文件就只抓FileNotFoundError,用户输入就只抓ValueError。
坑3:在finally里写return,吞掉异常
这个坑很隐蔽。如果try块里出了异常,但在finally里return了,异常会被吞掉,调用方完全不知道出过问题。finally一般只做"清理"工作(关文件、关连接),不要在里面return或抛新异常。
坑4:自己造异常,命名不规范
需要自定义异常时,类名要以Error结尾,比如UserNotFoundError,并且继承自Exception:
class UserNotFoundError(Exception): """用户不存在""" passraise UserNotFoundError("用户ID 1001 不存在")
坑5:用异常处理代替逻辑判断
能用if判断的就别用异常。比如判断一个文件存不存在,应该用os.path.exists(),而不是先try open然后except。异常是用来处理"意外"的,不是用来当普通流程控制的。
四、3个实战场景,看完就能用上
场景1:批量处理用户数据
假设你要处理一个用户列表,每个用户可能因为各种原因处理失败。一个失败不应该让整个程序停下来:
users = [{"name": "张三", "age": "25"}, {"name": "李四", "age": "abc"}]for user in users: try: age = int(user["age"]) print(f"{user['name']}今年{age}岁") except (ValueError, KeyError) as e: print(f"{user.get('name', '未知')}数据有问题:{e}")
这样张三会被正常处理,李四有问题会被跳过并打印提示,程序继续跑下去。
场景2:网络请求重试
爬虫或者调接口时,网络偶尔抽风很正常。我们可以用一个简单的重试机制:
import timedef fetch_data(url, max_retries=3): for i in range(max_retries): try: response = requests.get(url, timeout=5) return response.json() except requests.RequestException as e: print(f"第{i+1}次失败:{e}") time.sleep(2) raise Exception("重试3次后仍然失败")
场景3:资源清理,告别"文件句柄泄露"
前面finally的例子很经典,但Python有更优雅的写法——with语句。它能保证不管出不出错,文件都会被关掉:
try: with open("data.txt", "r", encoding="utf-8") as f: content = f.read() process(content)except FileNotFoundError: print("文件不存在")
推荐把所有"打开文件、连接数据库、申请锁"这类需要清理的操作,都用with包起来。这是Pythonic的写法,也是最不容易出错的写法。
说到底,代码能跑只是开始,能稳才算入门。一个真正合格的程序,不是在"理想情况下"能跑,而是在"用户乱搞、网络抽风、文件丢失"的情况下还能给出友好提示、不直接崩掉。
今天讲的是异常处理的"道",具体怎么用 try/except、怎么用 with、怎么自定义异常,都是"术"。道是"哪里该用、哪里不该用",术是"具体怎么写"。先有道,再练术,顺序不能反。
今日思考题:你去公司第一天,写了一个文件处理的脚本,测试时一切正常。但用户反馈说:跑一会儿就报"Too many open files"错误。你猜这是什么问题?用今天学的异常处理思路,你会怎么改?
看完有收获的话,欢迎在评论区留下你的答案。我会挑几位同学的思路详细点评。如果觉得有用,转发给身边正在学Python的朋友一起看~