第8章:Python 异常处理
8.1 异常的概念
异常是程序执行过程中发生的错误,会导致程序中断执行。Python 使用异常对象来表示异常情况。
常见的异常类型
| 异常类型 | 描述 |
|---|---|
Exception | 所有异常的基类 |
SyntaxError | 语法错误 |
NameError | 变量名不存在 |
TypeError | 类型错误 |
ValueError | 值错误 |
ZeroDivisionError | 除以零 |
FileNotFoundError | 文件不存在 |
PermissionError | 权限不足 |
IndexError | 索引越界 |
KeyError | 字典键不存在 |
异常的发生
当 Python 遇到错误时,会抛出异常。如果不处理异常,程序会终止并显示错误信息。
# 示例:除零异常
print(10 / 0) # 会抛出 ZeroDivisionError
# 示例:索引越界
numbers = [1, 2, 3]
print(numbers[10]) # 会抛出 IndexError
# 示例:类型错误
print("5" + 5) # 会抛出 TypeError
8.2 try-except 语句
try-except 语句用于捕获和处理异常。
基本语法
try:
# 可能会抛出异常的代码
pass
except ExceptionType:
# 处理特定类型的异常
pass
except:
# 处理所有其他类型的异常
pass
示例
# 捕获特定类型的异常
try:
result = 10 / 0
except ZeroDivisionError:
print("Error: Division by zero")
# 捕获多个异常
try:
numbers = [1, 2, 3]
print(numbers[10])
result = 10 / 0
except IndexError:
print("Error: Index out of range")
except ZeroDivisionError:
print("Error: Division by zero")
# 捕获所有异常
try:
# 可能会抛出异常的代码
pass
except Exception as e:
print(f"Error: {e}")
异常对象
可以使用 as 关键字来获取异常对象,从而获取更多关于异常的信息。
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Error type: {type(e).__name__}")
print(f"Error message: {e}")
8.3 else 和 finally 子句
else 子句
else 子句在 try 块没有抛出异常时执行。
try:
result = 10 / 2
except ZeroDivisionError:
print("Error: Division by zero")
else:
print(f"Result: {result}")
print("No exception occurred")
finally 子句
finally 子句无论是否发生异常都会执行,通常用于清理资源。
try:
file = open('example.txt', 'r')
content = file.read()
print(content)
except FileNotFoundError:
print("Error: File not found")
finally:
# 无论是否发生异常,都会执行这里的代码
if 'file' in locals() and file:
file.close()
print("File closed")
综合示例
try:
print("Trying to open file...")
file = open('example.txt', 'r')
except FileNotFoundError:
print("Error: File not found")
else:
print("File opened successfully")
content = file.read()
print(f"File content: {content[:50]}...")
finally:
if 'file' in locals() and file:
file.close()
print("File closed")
print("Operation completed")
8.4 自定义异常
可以通过继承 Exception 类来创建自定义异常。
基本语法
class CustomException(Exception):
"""自定义异常"""
pass
# 抛出自定义异常
raise CustomException("This is a custom exception")
示例
class InsufficientFundsError(Exception):
"""余额不足异常"""
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
self.message = f"Insufficient funds: balance = {balance}, amount = {amount}"
super().__init__(self.message)
# 测试
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(balance, amount)
return balance - amount
try:
balance = 1000
new_balance = withdraw(balance, 1500)
print(f"New balance: {new_balance}")
except InsufficientFundsError as e:
print(f"Error: {e}")
继承异常类
class BankingError(Exception):
"""银行错误基类"""
pass
class InsufficientFundsError(BankingError):
"""余额不足异常"""
pass
class InvalidAccountError(BankingError):
"""无效账户异常"""
pass
# 测试
try:
raise InsufficientFundsError("Balance too low")
except BankingError as e:
print(f"Banking error: {e}")
except Exception as e:
print(f"General error: {e}")
8.5 异常的最佳实践
1. 只捕获必要的异常
# 不好的做法
try:
# 可能会抛出多种异常的代码
pass
except Exception:
# 捕获所有异常,可能会掩盖真正的问题
pass
# 好的做法
try:
# 可能会抛出特定异常的代码
pass
except (ZeroDivisionError, ValueError) as e:
# 只捕获预期的异常
print(f"Error: {e}")
2. 提供具体的错误信息
# 不好的做法
try:
result = 10 / 0
except ZeroDivisionError:
print("Error occurred")
# 好的做法
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"Error: {e}")
# 或者更具体的信息
print("Error: Cannot divide by zero")
3. 使用 finally 清理资源
# 好的做法
try:
file = open('example.txt', 'r')
# 处理文件
except FileNotFoundError:
print("File not found")
finally:
if 'file' in locals() and file:
file.close()
# 更好的做法(使用 with 语句)
try:
with open('example.txt', 'r') as file:
# 处理文件
pass
except FileNotFoundError:
print("File not found")
# 文件会自动关闭
4. 不要过度使用异常
# 不好的做法
try:
value = int(input("Enter a number: "))
except ValueError:
print("Please enter a valid number")
# 好的做法(对于简单的输入验证)
while True:
user_input = input("Enter a number: ")
if user_input.isdigit():
value = int(user_input)
break
else:
print("Please enter a valid number")
5. 抛出有意义的异常
# 不好的做法
def calculate_discount(price, discount):
if discount < 0 or discount > 100:
raise Exception("Invalid discount")
return price * (1 - discount / 100)
# 好的做法
def calculate_discount(price, discount):
if discount < 0 or discount > 100:
raise ValueError("Discount must be between 0 and 100")
return price * (1 - discount / 100)
8.6 综合示例
示例1:安全的除法函数
def safe_divide(a, b):
"""安全的除法函数"""
try:
result = a / b
return result
except ZeroDivisionError:
print("Error: Division by zero")
return None
except TypeError:
print("Error: Both arguments must be numbers")
return None
# 测试
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # None
print(safe_divide(10, "2")) # None
示例2:文件处理
def read_file(filename):
"""读取文件内容"""
try:
with open(filename, 'r', encoding='utf-8') as file:
content = file.read()
return content
except FileNotFoundError:
print(f"Error: File '{filename}' not found")
return None
except PermissionError:
print(f"Error: Permission denied for '{filename}'")
return None
except UnicodeDecodeError:
print(f"Error: Unable to decode file '{filename}'")
return None
except Exception as e:
print(f"Error: {e}")
return None
# 测试
content = read_file('example.txt')
if content:
print(f"File content: {content[:100]}...")
示例3:用户输入验证
def get_integer_input(prompt):
"""获取整数输入"""
while True:
try:
value = int(input(prompt))
return value
except ValueError:
print("Please enter a valid integer")
# 测试
age = get_integer_input("Enter your age: ")
print(f"Your age is: {age}")
# 带范围验证
def get_integer_input_with_range(prompt, min_value, max_value):
"""获取指定范围内的整数输入"""
while True:
try:
value = int(input(prompt))
if min_value <= value <= max_value:
return value
else:
print(f"Please enter a value between {min_value} and {max_value}")
except ValueError:
print("Please enter a valid integer")
# 测试
score = get_integer_input_with_range("Enter your score (0-100): ", 0, 100)
print(f"Your score is: {score}")
示例4:自定义异常的使用
class InvalidEmailError(Exception):
"""无效邮箱异常"""
pass
def validate_email(email):
"""验证邮箱格式"""
if '@' not in email:
raise InvalidEmailError("Email must contain '@'")
if '.' not in email.split('@')[1]:
raise InvalidEmailError("Email must have a domain")
return True
try:
email = input("Enter your email: ")
validate_email(email)
print("Email is valid")
except InvalidEmailError as e:
print(f"Error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
8.7 常见问题和解决方案
问题1:捕获所有异常
# 错误示例
try:
# 代码
pass
except:
# 捕获所有异常,包括 KeyboardInterrupt 等
pass
# 解决方案
try:
# 代码
pass
except Exception as e:
# 只捕获 Exception 及其子类
print(f"Error: {e}")
问题2:异常处理过宽
# 错误示例
try:
# 多种可能的异常
result = 10 / int(input("Enter a number: "))
except Exception as e:
print(f"Error: {e}")
# 解决方案
try:
number = int(input("Enter a number: "))
result = 10 / number
except ValueError:
print("Please enter a valid integer")
except ZeroDivisionError:
print("Cannot divide by zero")
except Exception as e:
print(f"Unexpected error: {e}")
问题3:不处理异常
# 错误示例
def divide(a, b):
return a / b
# 调用时不处理异常
divide(10, 0) # 会崩溃
# 解决方案
def divide(a, b):
try:
return a / b
except ZeroDivisionError:
print("Cannot divide by zero")
return None
result = divide(10, 0)
if result is not None:
print(f"Result: {result}")
问题4:嵌套异常处理
# 不好的做法
try:
try:
# 代码
pass
except Exception as e:
print(f"Inner error: {e}")
except Exception as e:
print(f"Outer error: {e}")
# 好的做法
try:
# 代码
pass
except SpecificException as e:
print(f"Specific error: {e}")
except AnotherException as e:
print(f"Another error: {e}")
except Exception as e:
print(f"General error: {e}")
8.8 练习
- 异常处理练习:编写一个程序,计算两个数的除法,处理除零异常和类型错误
- 文件异常练习:编写一个程序,读取用户指定的文件,处理文件不存在和权限不足的异常
- 自定义异常练习:创建一个自定义异常,用于验证密码强度
- 输入验证练习:编写一个程序,获取用户输入的年龄,确保年龄是正数
- 综合练习:编写一个程序,从文件中读取数字,计算平均值,处理各种可能的异常
- 挑战练习:编写一个程序,模拟银行账户,处理余额不足等异常
8.9 小结
本章我们学习了:
- 异常的概念和常见类型
- try-except 语句的使用
- else 和 finally 子句
- 自定义异常
- 异常的最佳实践
- 综合示例
现在你已经掌握了 Python 的异常处理,可以开始学习 Python 的模块和包了!