← 返回首页 | ← 返回教程列表

Python学习教程 - 第八课:异常和错误处理,让程序更健壮!

欢迎回来!🎉 这节课我们要学习如何处理程序运行时可能出现的错误和异常,让我们的程序更健壮、更稳定!


复习一下前七节课

前七节课我们学会了: - 变量和基本数据类型 - 条件判断和循环 - 列表和字典 - 函数(代码的魔法师) - 文件操作(数据持久化) - 模块和包(代码组织) - 面向对象编程(OOP)

这节课我们要学习如何优雅地处理错误!


第一个知识点:什么是异常?

错误 vs 异常

语法错误(Syntax Error):代码写错了,Python根本无法运行
异常(Exception):代码语法正确,但运行时出现了问题

常见的异常类型

# ZeroDivisionError:除以零
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"错误:{e}")

# NameError:使用未定义的变量
try:
    print(undefined_variable)
except NameError as e:
    print(f"错误:{e}")

# TypeError:类型错误
try:
    result = "10" + 5
except TypeError as e:
    print(f"错误:{e}")

# ValueError:值错误
try:
    num = int("abc")
except ValueError as e:
    print(f"错误:{e}")

# IndexError:索引超出范围
try:
    lst = [1, 2, 3]
    print(lst[10])
except IndexError as e:
    print(f"错误:{e}")

# KeyError:字典键不存在
try:
    d = {"name": "小明"}
    print(d["age"])
except KeyError as e:
    print(f"错误:{e}")

# FileNotFoundError:文件不存在
try:
    with open("不存在的文件.txt", "r") as f:
        content = f.read()
except FileNotFoundError as e:
    print(f"错误:{e}")

# AttributeError:属性不存在
try:
    s = "hello"
    s.nonexistent_method()
except AttributeError as e:
    print(f"错误:{e}")

第二个知识点:try-except语句

基本语法

try:
    # 可能出错的代码
    result = 10 / 0
except ZeroDivisionError:
    # 捕获到异常时执行
    print("不能除以零!")

捕获多个异常

try:
    num = int(input("请输入一个数字:"))
    result = 100 / num
    print(f"结果是:{result}")
except ValueError:
    print("请输入有效的数字!")
except ZeroDivisionError:
    print("不能除以零!")
except Exception as e:
    print(f"发生了未知错误:{e}")

使用as获取异常信息

try:
    with open("test.txt", "r") as f:
        content = f.read()
except FileNotFoundError as e:
    print(f"文件不存在:{e}")
    print(f"错误类型:{type(e).__name__}")

第三个知识点:try-except-else-finally

else块:没有异常时执行

try:
    num = int(input("请输入一个数字:"))
except ValueError:
    print("请输入有效的数字!")
else:
    # 没有异常时执行
    print(f"你输入的数字是:{num}")
    print(f"数字的平方是:{num ** 2}")

finally块:无论如何都执行

try:
    f = open("test.txt", "r")
    content = f.read()
    print("文件读取成功!")
except FileNotFoundError:
    print("文件不存在!")
finally:
    # 无论是否有异常都会执行
    print("执行清理工作...")
    # f.close()  # 注意:如果文件没打开成功,这里会报错

完整的结构

try:
    # 尝试执行的代码
    num = int(input("请输入一个数字:"))
    result = 10 / num
except ValueError:
    # 处理ValueError
    print("请输入有效的整数!")
except ZeroDivisionError:
    # 处理ZeroDivisionError
    print("不能除以零!")
except Exception as e:
    # 处理其他所有异常
    print(f"发生了错误:{e}")
else:
    # 没有异常时执行
    print(f"计算成功!10 / {num} = {result}")
finally:
    # 无论如何都执行
    print("程序执行完毕!")

第四个知识点:抛出异常

raise语句

def divide(a, b):
    if b == 0:
        # 抛出异常
        raise ValueError("除数不能为零!")
    if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):
        raise TypeError("参数必须是数字!")
    return a / b

try:
    result = divide(10, 0)
except ValueError as e:
    print(f"错误:{e}")

try:
    result = divide("10", 2)
except TypeError as e:
    print(f"错误:{e}")

重新抛出异常

def process_data(data):
    try:
        result = int(data)
        return result
    except ValueError as e:
        print(f"处理数据时出错:{e}")
        # 重新抛出异常,让上层处理
        raise

try:
    process_data("abc")
except ValueError:
    print("上层捕获到异常")

第五个知识点:自定义异常

创建自定义异常类

# 自定义异常基类
class MyError(Exception):
    """我的自定义异常基类"""
    pass

# 具体的异常类
class InvalidAgeError(MyError):
    """年龄无效异常"""
    def __init__(self, age, message="年龄必须在0-150之间"):
        self.age = age
        self.message = message
        super().__init__(self.message)

    def __str__(self):
        return f"{self.message},你输入的是:{self.age}"

class InsufficientBalanceError(MyError):
    """余额不足异常"""
    def __init__(self, balance, amount, message="余额不足"):
        self.balance = balance
        self.amount = amount
        self.message = message
        super().__init__(self.message)

    def __str__(self):
        return f"{self.message}!需要:{self.amount},当前余额:{self.balance}"

# 使用自定义异常
class Person:
    def __init__(self, name, age):
        self.name = name
        if not (0 <= age <= 150):
            raise InvalidAgeError(age)
        self.age = age

class BankAccount:
    def __init__(self, balance=0):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientBalanceError(self.balance, amount)
        self.balance -= amount
        print(f"取款成功!剩余余额:{self.balance}")

# 测试
try:
    person = Person("小明", 200)
except InvalidAgeError as e:
    print(e)

try:
    account = BankAccount(100)
    account.withdraw(200)
except InsufficientBalanceError as e:
    print(e)

第六个知识点:异常链

使用raise...from

def load_config():
    try:
        with open("config.json", "r") as f:
            return f.read()
    except FileNotFoundError as e:
        # 异常链:从e引发新的异常
        raise RuntimeError("无法加载配置文件") from e

try:
    load_config()
except RuntimeError as e:
    print(f"错误:{e}")
    print(f"原因:{e.__cause__}")

隐式异常链

def func():
    try:
        1 / 0
    except:
        raise ValueError("出错了")  # 隐式保留原始异常

try:
    func()
except ValueError as e:
    print(f"捕获到:{e}")
    print(f"上下文:{e.__context__}")

第七个知识点:最佳实践

1. 只捕获你能处理的异常

# 不好的做法:捕获所有异常
try:
    # 一些代码
    pass
except:
    print("出错了")  # 不知道是什么错误

# 好的做法:只捕获特定异常
try:
    with open("file.txt", "r") as f:
        content = f.read()
except FileNotFoundError:
    print("文件不存在,请检查文件路径")

2. 提供有用的错误信息

# 不好的做法
try:
    int(input("输入数字:"))
except:
    print("错误")

# 好的做法
try:
    user_input = input("输入数字:")
    num = int(user_input)
except ValueError:
    print(f"错误:'{user_input}' 不是有效的整数,请重新输入")

3. 使用finally清理资源

# 方法1:try-finally
file = None
try:
    file = open("data.txt", "r")
    content = file.read()
finally:
    if file:
        file.close()

# 方法2:with语句(推荐)
with open("data.txt", "r") as file:
    content = file.read()

4. 不要过度使用异常

# 不好的做法:用异常控制流程
def get_item(dictionary, key):
    try:
        return dictionary[key]
    except KeyError:
        return None

# 好的做法:先检查
def get_item(dictionary, key):
    if key in dictionary:
        return dictionary[key]
    return None

# 或者使用get方法
value = dictionary.get(key, None)

综合练习:健壮的用户管理系统

让我们创建一个完善的用户管理系统,包含各种异常处理!

import re
from datetime import datetime

# 自定义异常
class UserManagementError(Exception):
    """用户管理异常基类"""
    pass

class InvalidUsernameError(UserManagementError):
    """用户名无效异常"""
    pass

class InvalidEmailError(UserManagementError):
    """邮箱无效异常"""
    pass

class InvalidPasswordError(UserManagementError):
    """密码无效异常"""
    pass

class UserNotFoundError(UserManagementError):
    """用户不存在异常"""
    pass

class UserAlreadyExistsError(UserManagementError):
    """用户已存在异常"""
    pass

# 用户类
class User:
    def __init__(self, username, email, password):
        self.username = self._validate_username(username)
        self.email = self._validate_email(email)
        self.password = self._validate_password(password)
        self.created_at = datetime.now()
        self.is_active = True

    def _validate_username(self, username):
        """验证用户名"""
        if not username:
            raise InvalidUsernameError("用户名不能为空")
        if len(username) < 3:
            raise InvalidUsernameError("用户名长度不能少于3个字符")
        if len(username) > 20:
            raise InvalidUsernameError("用户名长度不能超过20个字符")
        if not username.isalnum():
            raise InvalidUsernameError("用户名只能包含字母和数字")
        return username

    def _validate_email(self, email):
        """验证邮箱"""
        if not email:
            raise InvalidEmailError("邮箱不能为空")

        # 简单的邮箱正则
        email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(email_pattern, email):
            raise InvalidEmailError(f"邮箱格式无效:{email}")

        return email

    def _validate_password(self, password):
        """验证密码"""
        if not password:
            raise InvalidPasswordError("密码不能为空")
        if len(password) < 6:
            raise InvalidPasswordError("密码长度不能少于6个字符")

        # 检查是否包含至少一个字母和一个数字
        has_letter = any(c.isalpha() for c in password)
        has_digit = any(c.isdigit() for c in password)

        if not has_letter:
            raise InvalidPasswordError("密码必须包含至少一个字母")
        if not has_digit:
            raise InvalidPasswordError("密码必须包含至少一个数字")

        return password

    def __str__(self):
        return f"User(username='{self.username}', email='{self.email}', active={self.is_active})"

# 用户管理器
class UserManager:
    def __init__(self):
        self.users = {}  # username -> User

    def add_user(self, username, email, password):
        """添加用户"""
        if username in self.users:
            raise UserAlreadyExistsError(f"用户 '{username}' 已存在")

        try:
            user = User(username, email, password)
            self.users[username] = user
            print(f"✅ 用户 '{username}' 创建成功!")
            return user
        except (InvalidUsernameError, InvalidEmailError, InvalidPasswordError) as e:
            print(f"❌ 创建用户失败:{e}")
            raise

    def get_user(self, username):
        """获取用户"""
        if username not in self.users:
            raise UserNotFoundError(f"用户 '{username}' 不存在")
        return self.users[username]

    def update_email(self, username, new_email):
        """更新邮箱"""
        user = self.get_user(username)
        try:
            user.email = user._validate_email(new_email)
            print(f"✅ 用户 '{username}' 邮箱更新成功!")
        except InvalidEmailError as e:
            print(f"❌ 更新邮箱失败:{e}")
            raise

    def update_password(self, username, old_password, new_password):
        """更新密码"""
        user = self.get_user(username)

        if user.password != old_password:
            raise InvalidPasswordError("原密码不正确")

        try:
            user.password = user._validate_password(new_password)
            print(f"✅ 用户 '{username}' 密码更新成功!")
        except InvalidPasswordError as e:
            print(f"❌ 更新密码失败:{e}")
            raise

    def delete_user(self, username):
        """删除用户"""
        if username not in self.users:
            raise UserNotFoundError(f"用户 '{username}' 不存在")

        del self.users[username]
        print(f"✅ 用户 '{username}' 删除成功!")

    def list_users(self):
        """列出所有用户"""
        if not self.users:
            print("当前没有用户")
            return

        print("\n=== 用户列表 ===")
        for i, (username, user) in enumerate(self.users.items(), 1):
            print(f"{i}. {user}")

# 主程序
def main():
    print("="*60)
    print("  👥  用户管理系统")
    print("="*60)

    manager = UserManager()

    # 演示各种操作
    print("\n--- 1. 添加用户 ---")
    try:
        manager.add_user("xiaoming", "xiaoming@example.com", "password123")
        manager.add_user("xiaohong", "xiaohong@example.com", "secure456")
        manager.add_user("invalid", "bad-email", "short")  # 这个会失败
    except UserManagementError as e:
        print(f"操作失败:{e}")

    print("\n--- 2. 列出用户 ---")
    manager.list_users()

    print("\n--- 3. 查询用户 ---")
    try:
        user = manager.get_user("xiaoming")
        print(f"找到用户:{user}")
    except UserNotFoundError as e:
        print(e)

    print("\n--- 4. 更新邮箱 ---")
    try:
        manager.update_email("xiaoming", "xiaoming_new@example.com")
        manager.update_email("xiaohong", "invalid-email")  # 失败
    except UserManagementError as e:
        print(e)

    print("\n--- 5. 更新密码 ---")
    try:
        manager.update_password("xiaoming", "password123", "newpass789")
        manager.update_password("xiaohong", "wrongpassword", "newpass")  # 失败
    except UserManagementError as e:
        print(e)

    print("\n--- 6. 删除用户 ---")
    try:
        manager.delete_user("xiaohong")
        manager.delete_user("nonexistent")  # 失败
    except UserManagementError as e:
        print(e)

    print("\n--- 7. 最终用户列表 ---")
    manager.list_users()

    print("\n" + "="*60)
    print("  🎉  演示完成!")
    print("="*60)

if __name__ == "__main__":
    main()

第八课小结

今天我们学会了:

异常的概念:运行时出现的问题
常见异常类型:ZeroDivisionError、ValueError、KeyError等
try-except语句:捕获和处理异常
else和finally:无异常时执行,无论如何都执行
抛出异常:raise语句
自定义异常:创建自己的异常类
异常链:保留异常上下文
最佳实践:合理使用异常,提供有用信息


课后练习 ✏️

  1. 写一个函数,安全地读取用户输入的数字,包含完整的异常处理
  2. 创建一个简单的计算器程序,处理各种可能的异常
  3. 自定义几个异常类,创建一个简单的银行账户系统
  4. 写一个函数,尝试打开并读取文件,包含完整的异常处理和资源清理
  5. 创建一个配置文件加载器,处理文件不存在、格式错误等异常

下节课预告:我们会学习正则表达式,强大的文本处理工具!

继续加油!你已经掌握了Python编程的核心技能!💪