Python3 错误和异常:别怕报错,那是程序在跟你说话
我是陈默,一个正拼命上岸的码农。
你写代码最怕什么?
报错。
红色的错误信息一出来,心跳加速,手心冒汗。
但说实话——报错不是坏事。那是程序在跟你说话。它在告诉你:"嘿,这里有问题,你得修一下。"
真正可怕的不是报错,是你的代码默默跑完,结果全是错的。
今天我们来聊聊 Python 的错误和异常处理。学会它,你的代码就不会因为一个小意外就崩溃。
1. 两种错误:语法错误和异常
语法错误(SyntaxError)
代码写错了,Python 看不懂。
# 少了冒号ifTrue print("hello")# SyntaxError: expected ':'# 括号没配对print("hello"# SyntaxError: unexpected EOF while parsing# 缩进错误deffunc():print("hello")# IndentedError: expected an indented block
语法错误在运行前就能发现。修复很简单——看报错行号,回去改。
异常(Exception)
语法没问题,但运行时出了状况。
print(1 / 0) # ZeroDivisionError:除以零print(undefined_var) # NameError:变量不存在int("abc") # ValueError:转换失败lst = [1, 2, 3]print(lst[10]) # IndexError:索引越界d = {"name": "陈默"}print(d["age"]) # KeyError:键不存在
异常才是我们今天要重点讲的——因为它是可以被处理的。
2. try-except:捕获异常
基本用法
try: result = 10 / 0except ZeroDivisionError: print("不能除以零")print("程序继续运行") # 输出: 程序继续运行
没有 try-except,程序直接崩溃。有了它,程序优雅地处理问题,继续往下走。
捕获多种异常
try: num = int(input("输入数字:")) result = 100 / num print(f"结果:{result}")except ValueError: print("请输入有效的数字")except ZeroDivisionError: print("不能除以零")
捕获所有异常
try:# 一些可能出错的代码 result = do_something()except Exception as e: print(f"出错了:{e}")
注意:except Exception 可以捕获所有异常,但别滥用。
# ❌ 万能捕获,出问题了都不知道为什么try: result = complex_operation()except:pass# 吞掉所有错误,非常危险# ✅ 明确捕获你知道的异常try: result = int(user_input)except ValueError: print("输入格式错误")
获取异常信息
try: result = 10 / 0except ZeroDivisionError as e: print(f"错误类型:{type(e).__name__}") # 输出: 错误类型:ZeroDivisionError print(f"错误信息:{e}") # 输出: 错误信息:division by zero
3. try-except-else-finally:完整的结构
try:# 可能出错的代码 f = open("data.txt", "r") content = f.read()except FileNotFoundError:# 出错时执行 print("文件不存在")else:# 没出错时执行 print(f"文件内容:{content}")finally:# 不管出不出错都执行 print("清理工作完成")
执行逻辑:
finally 的常见用途:清理资源
try: f = open("data.txt", "r") content = f.read()finally: f.close() # 不管出不出错,都关闭文件
不过我们之前学过,用 with 更好:
with open("data.txt", "r") as f: content = f.read() # 自动关闭,不用 finally
4. 常见异常类型
| | |
|---|
ZeroDivisionError | | 1 / 0 |
ValueError | | int("abc") |
TypeError | | "2" + 2 |
IndexError | | [1,2][5] |
KeyError | | {}["x"] |
NameError | | print(x) |
FileNotFoundError | | open("xx") |
AttributeError | | "hi".appendd() |
ImportError | | import 不存在的模块 |
实际例子
# TypeError:类型不对print("年龄:" + 25) # 报错!字符串不能直接加数字print("年龄:" + str(25)) # 输出: 年龄:25# AttributeError:方法名写错name = "陈默"print(name.uppper()) # 报错!没有 uppper 方法print(name.upper()) # 输出: 陈默# IndexError:索引越界nums = [1, 2, 3]print(nums[5]) # 报错!只有 3 个元素
5. 主动抛出异常:raise
有时候你自己需要抛出异常。
基本用法
defset_age(age):if age < 0:raise ValueError("年龄不能为负数")if age > 150:raise ValueError("年龄不太对吧?")return agetry: set_age(-5)except ValueError as e: print(e) # 输出: 年龄不能为负数
自定义异常
classInsufficientFundsError(Exception):"""余额不足异常"""def__init__(self, balance, amount): self.balance = balance self.amount = amount super().__init__(f"余额不足:余额 {balance} 元,尝试取出 {amount} 元")classBankAccount:def__init__(self, balance=0): self.balance = balancedefwithdraw(self, amount):if amount > self.balance:raise InsufficientFundsError(self.balance, amount) self.balance -= amountreturn self.balance# 使用account = BankAccount(100)try: account.withdraw(200)except InsufficientFundsError as e: print(e) # 输出: 余额不足:余额 100 元,尝试取出 200 元except Exception as e: print(f"其他错误:{e}")
自定义异常让你的代码更专业。别人调用你的函数时,能清楚地知道出了什么问题。
6. 异常链:从底层到上层
defread_config():try:with open("config.json", "r") as f:return f.read()except FileNotFoundError as e:# 把底层异常包装成更有意义的异常raise RuntimeError("配置文件缺失,请检查") from etry: config = read_config()except RuntimeError as e: print(e) # 输出: 配置文件缺失,请检查 print(e.__cause__) # 输出: [Errno 2] No such file or directory: 'config.json'
raise ... from e 保留了原始异常信息,方便排查问题。
7. 常见场景:异常处理最佳实践
场景一:用户输入
defget_int(prompt):"""安全地获取整数输入"""whileTrue:try:return int(input(prompt))except ValueError: print("请输入有效的整数")age = get_int("请输入年龄:")print(f"你的年龄:{age}")
场景二:文件操作
defsafe_read(filepath):"""安全地读取文件"""try:with open(filepath, "r", encoding="utf-8") as f:return f.read()except FileNotFoundError: print(f"文件不存在:{filepath}")returnNoneexcept PermissionError: print(f"没有权限读取:{filepath}")returnNoneexcept UnicodeDecodeError: print(f"文件编码错误:{filepath}")returnNonecontent = safe_read("data.txt")if content: print(content)
场景三:网络请求
deffetch_data(url, retries=3):"""带重试的网络请求"""import timefor attempt in range(retries):try:# 模拟请求if url == "success":return {"status": "ok"}else:raise ConnectionError("连接失败")except ConnectionError as e:if attempt < retries - 1: print(f"第{attempt + 1}次失败,1秒后重试...") time.sleep(1)else: print(f"重试{retries}次后仍然失败")raisetry: data = fetch_data("fail")except ConnectionError: print("请检查网络连接")
8. 实战:带异常处理的成绩管理器
classScoreManager:def__init__(self): self.scores = {}defadd(self, name, score):ifnot isinstance(score, (int, float)):raise TypeError("分数必须是数字")ifnot0 <= score <= 100:raise ValueError("分数必须在 0-100 之间")if name in self.scores:raise ValueError(f"{name} 的成绩已存在") self.scores[name] = score print(f"添加成功:{name}{score}分")defget(self, name):if name notin self.scores:raise KeyError(f"找不到学生:{name}")return self.scores[name]defreport(self):ifnot self.scores:raise ValueError("还没有成绩数据") avg = sum(self.scores.values()) / len(self.scores) print(f"人数:{len(self.scores)}") print(f"平均:{avg:.1f}") print(f"最高:{max(self.scores.values())}") print(f"最低:{min(self.scores.values())}")# 使用manager = ScoreManager()whileTrue:try: name = input("姓名(q退出):")if name.lower() == "q":break score = float(input("分数:")) manager.add(name, score)except (TypeError, ValueError) as e: print(f"输入错误:{e}")except KeyError as e: print(f"错误:{e}")try: manager.report()except ValueError as e: print(e)
最后
异常处理不是让你的代码"不报错",而是让你的代码"出错了也能优雅地应对"。
记住三件事:
- 只捕获你预期会发生的异常,别用空的
except: pass - 用
raise 主动抛出异常,让调用者知道出了什么问题
我的建议:
把你写过的代码拿出来,找到那些直接使用用户输入或读取文件的地方。给它们加上 try-except,让程序在出错时给出友好的提示,而不是崩溃退出。
好的代码不是不出错,而是出了错也能从容应对。
今天就到这里。
我是陈默,我们下期再见。
如果你觉得这篇文章有帮助,欢迎关注我。我会持续分享 Python 学习的干货。