错误处理让你的代码在遇到问题时不会立即崩溃,而是优雅地处理异常情况,继续运行或给出有意义的提示
为什么错误处理如此重要?
看看这个没有错误处理的危险代码:
python
# 危险!没有错误处理的代码
def calculate_discount(price, discount):
return price * (1 - discount)
# 正常情况没问题
print(calculate_discount(100, 0.2)) # 输出: 80.0
# 但如果有异常输入...
print(calculate_discount(100, "20%")) # 程序崩溃!
print(calculate_discount("100元", 0.2)) # 程序崩溃!
一次意外的输入,整个程序就崩溃了
Python错误处理基础:try-except的魔力
基本用法
python
def safe_calculate_discount(price, discount):
try:
# 尝试执行可能出错的代码
result = price * (1 - discount)
return result
except TypeError:
# 如果发生类型错误,执行这里
print("错误:价格和折扣必须是数字!")
return None
# 现在程序不会崩溃了
print(safe_calculate_discount(100, 0.2)) # 正常: 80.0
print(safe_calculate_discount(100, "20%")) # 优雅处理: 错误:价格和折扣必须是数字! None
处理多种异常
python
def process_user_data(user_data):
try:
name = user_data["name"]
age = user_data["age"]
email = user_data["email"]
# 模拟一些业务逻辑
if age < 0:
raise ValueError("年龄不能为负数")
return f"用户 {name} ({age}岁) 邮箱: {email}"
except KeyError as e:
print(f"数据不完整:缺少字段 {e}")
return None
except ValueError as e:
print(f"数据验证失败:{e}")
return None
except Exception as e:
print(f"未知错误:{e}")
return None
# 测试各种异常情况
test_cases = [
{"name": "张三", "age": 25, "email": "zhang@example.com"}, # 正常
{"name": "李四", "age": -5, "email": "li@example.com"}, # 年龄为负
{"name": "王五", "age": 30}, # 缺少邮箱
"invalid data" # 完全错误的数据
]
for data in test_cases:
result = process_user_data(data)
print(f"处理结果: {result}")
高级错误处理技巧
else和finally子句
pythondef
"""读取配置文件,演示完整的错误处理结构"""
config = {}
file = None
try:
print(f"尝试打开文件: {filename}")
file = open(filename, 'r', encoding='utf-8')
content = file.read()
config = eval(content) # 这里可能出错
except FileNotFoundError:
print(f"文件 {filename} 不存在")
except SyntaxError:
print("配置文件语法错误")
except Exception as e:
print(f"读取文件时发生错误: {e}")
else:
# 只有在try块成功执行时才运行
print("配置文件读取成功!")
finally:
# 无论是否发生异常都会执行
if file:
file.close()
print("文件已关闭")
return config
# 测试
config = read_config_file("config.txt")
print(f"配置内容: {config}")
自定义异常类
python
class BusinessError(Exception):
"""业务逻辑异常基类"""
pass
class InsufficientFundsError(BusinessError):
"""余额不足异常"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"余额不足!当前余额: {balance}, 需要: {amount}")
class InvalidAmountError(BusinessError):
"""无效金额异常"""
def __init__(self, amount):
self.amount = amount
super().__init__(f"无效金额: {amount},必须大于0")
class BankAccount:
def __init__(self, account_holder, initial_balance=0):
self.account_holder = account_holder
self.balance = initial_balance
def withdraw(self, amount):
"""取款操作"""
if amount <= 0:
raise InvalidAmountError(amount)
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
print(f"成功取款 {amount},当前余额: {self.balance}")
return amount
# 使用自定义异常
account = BankAccount("张三", 1000)
test_amounts = [500, 1500, -100]
for amount in test_amounts:
try:
account.withdraw(amount)
except BusinessError as e:
print(f"业务操作失败: {e}")
except Exception as e:
print(f"系统错误: {e}")
实际应用场景
场景1:API调用错误处理
python
import requests
import time
def robust_api_call(url, max_retries=3, timeout=5):
"""健壮的API调用,包含重试机制"""
for attempt in range(max_retries):
try:
print(f"第 {attempt + 1} 次尝试调用 API...")
response = requests.get(url, timeout=timeout)
response.raise_for_status() # 如果状态码不是200,抛出异常
# 成功获取响应
data = response.json()
print("API调用成功!")
return data
except requests.exceptions.Timeout:
print(f"请求超时 (尝试 {attempt + 1}/{max_retries})")
except requests.exceptions.ConnectionError:
print(f"连接错误 (尝试 {attempt + 1}/{max_retries})")
except requests.exceptions.HTTPError as e:
print(f"HTTP错误: {e.response.status_code}")
# 4xx错误不需要重试
if 400 <= e.response.status_code < 500:
break
except Exception as e:
print(f"未知错误: {e}")
# 等待后重试
if attempt < max_retries - 1:
wait_time = 2 ** attempt # 指数退避
print(f"等待 {wait_time} 秒后重试...")
time.sleep(wait_time)
print("所有重试尝试都失败了")
return None
# 测试
# result = robust_api_call("https://api.example.com/data")
场景2:数据库操作错误处理
python
import sqlite3
from contextlib import contextmanager
@contextmanager
def database_connection(db_path):
"""数据库连接上下文管理器,自动处理连接和事务"""
conn = None
try:
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row # 返回字典样式的行
print("数据库连接成功")
yield conn
conn.commit()
print("事务提交成功")
except sqlite3.Error as e:
if conn:
conn.rollback()
print(f"数据库错误,已回滚: {e}")
raise
finally:
if conn:
conn.close()
print("数据库连接已关闭")
def safe_database_operation():
"""安全的数据库操作示例"""
try:
with database_connection(":memory:") as conn:
# 创建表
conn.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL
)
''')
# 插入数据
users = [
(1, "张三", "zhang@example.com"),
(2, "李四", "li@example.com"),
(3, "王五", "wang@example.com")
]
for user in users:
try:
conn.execute(
"INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
user
)
print(f"成功插入用户: {user[1]}")
except sqlite3.IntegrityError:
print(f"用户 {user[1]} 已存在,跳过插入")
# 查询数据
cursor = conn.execute("SELECT * FROM users")
for row in cursor:
print(f"ID: {row['id']}, 姓名: {row['name']}, 邮箱: {row['email']}")
except Exception as e:
print(f"数据库操作失败: {e}")
# 运行示例
safe_database_operation()
场景3:文件处理错误处理
python
import os
import json
def safe_file_operations():
"""安全的文件操作示例"""
def read_json_file(filename):
"""安全读取JSON文件"""
try:
with open(filename, 'r', encoding='utf-8') as file:
data = json.load(file)
print(f"成功读取文件: {filename}")
return data
except FileNotFoundError:
print(f"文件 {filename} 不存在")
return {}
except json.JSONDecodeError as e:
print(f"JSON解析错误: {e}")
return {}
except PermissionError:
print(f"没有权限读取文件: {filename}")
return {}
def write_json_file(filename, data):
"""安全写入JSON文件"""
try:
# 确保目录存在
os.makedirs(os.path.dirname(filename) or '.', exist_ok=True)
with open(filename, 'w', encoding='utf-8') as file:
json.dump(data, file, indent=2, ensure_ascii=False)
print(f"成功写入文件: {filename}")
return True
except PermissionError:
print(f"没有权限写入文件: {filename}")
return False
except Exception as e:
print(f"写入文件时出错: {e}")
return False
# 测试文件操作
test_data = {
"users": [
{"name": "张三", "age": 25},
{"name": "李四", "age": 30}
],
"settings": {
"theme": "dark",
"language": "zh-CN"
}
}
# 写入文件
if write_json_file("data/config.json", test_data):
# 读取文件
loaded_data = read_json_file("data/config.json")
print(f"读取的数据: {loaded_data}")
# 测试错误情况
read_json_file("nonexistent_file.json") # 文件不存在
read_json_file("/root/protected_file.json") # 权限错误
# 运行示例
safe_file_operations()
错误处理的最佳实践
1. 具体化异常捕获
python
# 不好 - 太宽泛
try:
# 一些操作
except:
pass
# 好 - 具体异常
try:
# 一些操作
except (ValueError, TypeError) as e:
print(f"输入数据错误: {e}")
2. 不要静默吞噬异常
python
# 不好 - 静默吞噬异常
try:
risky_operation()
except:
pass # 没人知道这里出错了
# 好 - 记录或处理异常
try:
risky_operation()
except Exception as e:
logger.error(f"操作失败: {e}")
# 或者给用户反馈
3. 使用异常链
python
def process_data(data):
try:
return complex_operation(data)
except Exception as e:
# 保留原始异常信息
raise ProcessingError("数据处理失败") from e