Python 错误和异常处理

Python 错误和异常处理

目录


错误与异常概述

什么是错误?

程序运行时出现的问题分为两类:

  1. 语法错误(Syntax Errors):代码不符合 Python 语法规则
  2. 异常(Exceptions):运行时错误,语法正确但执行出错
python 复制代码
# ❌ 语法错误 - 程序无法运行
print("Hello"  # 缺少右括号

# ✅ 异常 - 程序可以运行,但会出错
print(1 / 0)  # ZeroDivisionError

为什么需要异常处理?

python 复制代码
# ❌ 没有异常处理 - 程序崩溃
def divide(a, b):
    return a / b

result = divide(10, 0)  # 程序崩溃!
print("这行不会执行")

# ✅ 有异常处理 - 优雅处理错误
def safe_divide(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        print("除数不能为零")
        return None

result = safe_divide(10, 0)
print("程序继续运行")  # 这行会执行

语法错误

常见语法错误

python 复制代码
# 1. 缺少冒号
# if True  # SyntaxError
#     print("Hello")

# 2. 缩进错误
# def hello():
# print("Hello")  # IndentationError

# 3. 括号不匹配
# print((1 + 2)  # SyntaxError

# 4. 引号不匹配
# text = "Hello'  # SyntaxError

# 5. 关键字拼写错误
# fro i in range(10):  # SyntaxError
#     print(i)

解读语法错误信息

python 复制代码
# 运行以下代码会产生错误信息
# print("Hello"

"""
错误信息示例:
  File "example.py", line 1
    print("Hello"
                ^
SyntaxError: unexpected EOF while parsing

解读:
- File "example.py", line 1: 错误发生在 example.py 第 1 行
- SyntaxError: 错误类型是语法错误
- unexpected EOF while parsing: 具体错误描述
- ^ 符号指向出错位置
"""

异常类型

Python 内置异常层次结构

php 复制代码
BaseException
 ├── SystemExit
 ├── KeyboardInterrupt
 ├── GeneratorExit
 └── Exception
      ├── ArithmeticError
      │    ├── FloatingPointError
      │    ├── OverflowError
      │    └── ZeroDivisionError
      ├── AssertionError
      ├── AttributeError
      ├── BufferError
      ├── EOFError
      ├── ImportError
      ├── LookupError
      │    ├── IndexError
      │    └── KeyError
      ├── MemoryError
      ├── NameError
      ├── OSError
      │    ├── FileNotFoundError
      │    ├── PermissionError
      │    └── TimeoutError
      ├── RuntimeError
      ├── StopIteration
      ├── SyntaxError
      ├── TypeError
      ├── ValueError
      └── ...

常见异常类型

python 复制代码
# 1. ZeroDivisionError - 除以零
# result = 1 / 0

# 2. TypeError - 类型错误
# result = "hello" + 5

# 3. ValueError - 值错误
# number = int("abc")

# 4. IndexError - 索引超出范围
# my_list = [1, 2, 3]
# print(my_list[10])

# 5. KeyError - 字典键不存在
# my_dict = {"a": 1}
# print(my_dict["b"])

# 6. AttributeError - 属性不存在
# my_list = [1, 2, 3]
# my_list.appendx(4)

# 7. FileNotFoundError - 文件不存在
# file = open("nonexistent.txt")

# 8. ImportError - 导入错误
# import nonexistent_module

# 9. NameError - 变量未定义
# print(undefined_variable)

# 10. AssertionError - 断言失败
# assert False, "断言失败"

查看异常信息

python 复制代码
try:
    result = 1 / 0
except ZeroDivisionError as e:
    print(f"异常类型: {type(e).__name__}")
    print(f"异常信息: {e}")
    print(f"异常详情: {repr(e)}")

# 输出:
# 异常类型: ZeroDivisionError
# 异常信息: division by zero
# 异常详情: ZeroDivisionError('division by zero')

异常处理基础

try-except 基本语法

python 复制代码
try:
    # 可能抛出异常的代码
    result = 10 / 0
except ZeroDivisionError:
    # 处理特定异常
    print("捕获到除零错误")

print("程序继续执行")

捕获多个异常

python 复制代码
# 方法1:多个 except 块
try:
    number = int(input("请输入数字: "))
    result = 10 / number
except ValueError:
    print("输入的不是有效数字")
except ZeroDivisionError:
    print("不能除以零")

# 方法2:一个 except 捕获多个异常类型
try:
    number = int(input("请输入数字: "))
    result = 10 / number
except (ValueError, ZeroDivisionError) as e:
    print(f"发生错误: {e}")

捕获所有异常

python 复制代码
# ⚠️ 不推荐:捕获所有异常会隐藏问题
try:
    # 一些操作
    pass
except Exception as e:
    print(f"发生未知错误: {e}")

# ✅ 推荐:只捕获预期的异常
try:
    result = 10 / int(user_input)
except (ValueError, ZeroDivisionError) as e:
    print(f"输入错误: {e}")

try-except 详解

完整的 try 语句结构

python 复制代码
try:
    # 可能抛出异常的代码
    print("尝试执行")
except ValueError as e:
    # 处理特定异常
    print(f"值错误: {e}")
except TypeError as e:
    # 处理另一种异常
    print(f"类型错误: {e}")
else:
    # 没有异常时执行
    print("成功执行")
finally:
    # 无论是否异常都执行
    print("清理工作")

异常处理流程

python 复制代码
def demonstrate_flow(divisor):
    """演示异常处理流程"""
    try:
        print("1. 进入 try 块")
        result = 10 / divisor
        print(f"2. 计算结果: {result}")
    except ZeroDivisionError:
        print("3. 捕获到 ZeroDivisionError")
        result = None
    except Exception as e:
        print(f"3. 捕获到其他异常: {e}")
        result = None
    else:
        print("4. 没有异常,执行 else")
    finally:
        print("5. 执行 finally(总是执行)")

    print(f"6. 函数结束,result = {result}\n")
    return result

demonstrate_flow(2)   # 正常情况
demonstrate_flow(0)   # 异常情况

嵌套异常处理

python 复制代码
try:
    print("外层 try")
    try:
        print("内层 try")
        result = 1 / 0
    except ZeroDivisionError:
        print("内层 except: 捕获除零错误")
        raise  # 重新抛出异常
except ZeroDivisionError:
    print("外层 except: 再次捕获除零错误")
finally:
    print("外层 finally")

# 输出:
# 外层 try
# 内层 try
# 内层 except: 捕获除零错误
# 外层 except: 再次捕获除零错误
# 外层 finally

finally 子句

finally 的作用

finally 块中的代码无论是否发生异常都会执行,常用于清理资源。

python 复制代码
# 文件操作示例
file = None
try:
    file = open("test.txt", "w")
    file.write("Hello, World!")
except IOError as e:
    print(f"文件操作错误: {e}")
finally:
    # 确保文件被关闭
    if file:
        file.close()
        print("文件已关闭")

数据库连接示例

python 复制代码
import sqlite3

def query_database(sql):
    conn = None
    cursor = None
    try:
        conn = sqlite3.connect("example.db")
        cursor = conn.cursor()
        cursor.execute(sql)
        results = cursor.fetchall()
        return results
    except sqlite3.Error as e:
        print(f"数据库错误: {e}")
        return []
    finally:
        # 确保资源被释放
        if cursor:
            cursor.close()
        if conn:
            conn.close()
        print("数据库连接已关闭")

finally 中的 return

python 复制代码
def test_finally_return():
    try:
        print("try 块")
        return "from try"
    except:
        print("except 块")
        return "from except"
    finally:
        print("finally 块")
        # ⚠️ finally 中的 return 会覆盖其他的 return
        # return "from finally"

result = test_finally_return()
print(f"返回值: {result}")

# 输出:
# try 块
# finally 块
# 返回值: from try

else 子句

else 的作用

else 块在没有异常发生时执行,用于区分正常流程和异常处理。

python 复制代码
def read_config(filename):
    try:
        with open(filename, 'r') as f:
            config = f.read()
    except FileNotFoundError:
        print(f"配置文件 {filename} 不存在")
        return {}
    else:
        # 只有在文件成功读取时才执行
        print("配置文件读取成功")
        return parse_config(config)

def parse_config(content):
    """解析配置内容"""
    # 模拟解析
    return {"key": "value"}

else vs 直接在 try 中

python 复制代码
# ❌ 不推荐:异常处理范围过大
try:
    file = open("data.txt")
    data = file.read()
    result = process_data(data)  # 如果这里出错,会被误认为是文件错误
    file.close()
except IOError:
    print("IO 错误")

# ✅ 推荐:使用 else 缩小异常处理范围
try:
    file = open("data.txt")
except IOError:
    print("文件打开失败")
else:
    try:
        data = file.read()
        result = process_data(data)
    finally:
        file.close()

抛出异常

raise 语句

python 复制代码
# 1. 抛出内置异常
def set_age(age):
    if age < 0 or age > 150:
        raise ValueError(f"无效的年龄: {age}")
    return age

try:
    set_age(-5)
except ValueError as e:
    print(e)  # 无效的年龄: -5

带消息的异常

python 复制代码
# 2. 抛出自定义消息
def withdraw(balance, amount):
    if amount <= 0:
        raise ValueError("取款金额必须大于零")
    if amount > balance:
        raise ValueError(f"余额不足: 余额 {balance}, 请求 {amount}")
    return balance - amount

try:
    withdraw(100, 150)
except ValueError as e:
    print(f"错误: {e}")

重新抛出异常

python 复制代码
def process_data(data):
    try:
        result = int(data)
    except ValueError as e:
        print(f"转换失败: {e}")
        raise  # 重新抛出原始异常

try:
    process_data("abc")
except ValueError:
    print("捕获到重新抛出的异常")

异常转换

python 复制代码
def read_config_file(filename):
    try:
        with open(filename, 'r') as f:
            return f.read()
    except FileNotFoundError as e:
        # 转换为更具体的业务异常
        raise ConfigError(f"配置文件 {filename} 不存在") from e

class ConfigError(Exception):
    """配置错误"""
    pass

自定义异常

创建自定义异常类

python 复制代码
# 1. 基本自定义异常
class MyError(Exception):
    """我的自定义异常"""
    pass

raise MyError("出错了")

带属性的自定义异常

python 复制代码
class ValidationError(Exception):
    """验证错误"""

    def __init__(self, field, message, value=None):
        self.field = field
        self.message = message
        self.value = value
        super().__init__(f"{field}: {message} (值: {value})")

# 使用
try:
    raise ValidationError("email", "邮箱格式不正确", "invalid@email")
except ValidationError as e:
    print(f"字段: {e.field}")
    print(f"消息: {e.message}")
    print(f"值: {e.value}")
    print(f"完整信息: {e}")

异常层次结构

python 复制代码
# 创建异常层次结构
class AppError(Exception):
    """应用基础异常"""
    pass

class DatabaseError(AppError):
    """数据库错误"""
    pass

class ConnectionError(DatabaseError):
    """连接错误"""
    pass

class QueryError(DatabaseError):
    """查询错误"""
    pass

class BusinessError(AppError):
    """业务逻辑错误"""
    pass

class InsufficientFundsError(BusinessError):
    """余额不足"""
    pass

# 使用:可以捕获特定异常或父类异常
try:
    raise InsufficientFundsError("余额不足")
except BusinessError as e:
    print(f"业务错误: {e}")
except AppError as e:
    print(f"应用错误: {e}")

实际示例:用户注册验证

python 复制代码
class RegistrationError(Exception):
    """注册错误基类"""
    pass

class UsernameError(RegistrationError):
    """用户名错误"""
    pass

class EmailError(RegistrationError):
    """邮箱错误"""
    pass

class PasswordError(RegistrationError):
    """密码错误"""
    pass

def validate_username(username):
    if not username:
        raise UsernameError("用户名不能为空", username)
    if len(username) < 3:
        raise UsernameError("用户名至少3个字符", username)
    if not username.isalnum():
        raise UsernameError("用户名只能包含字母和数字", username)
    return True

def validate_email(email):
    if not email:
        raise EmailError("邮箱不能为空", email)
    if "@" not in email or "." not in email:
        raise EmailError("邮箱格式不正确", email)
    return True

def validate_password(password):
    if not password:
        raise PasswordError("密码不能为空", password)
    if len(password) < 8:
        raise PasswordError("密码至少8个字符", password)
    if not any(c.isdigit() for c in password):
        raise PasswordError("密码必须包含数字", password)
    if not any(c.isalpha() for c in password):
        raise PasswordError("密码必须包含字母", password)
    return True

def register_user(username, email, password):
    """注册用户"""
    try:
        validate_username(username)
        validate_email(email)
        validate_password(password)
        print(f"✓ 用户 {username} 注册成功")
        return True
    except RegistrationError as e:
        print(f"✗ 注册失败: {e}")
        return False

# 测试
register_user("ab", "test@example.com", "password123")  # 用户名太短
register_user("john", "invalid", "password123")         # 邮箱格式错误
register_user("john", "john@example.com", "12345678")   # 密码无字母
register_user("john", "john@example.com", "Password1")  # 成功

异常链

from 关键字

python 复制代码
# 异常链:保留原始异常信息
def process_file(filename):
    try:
        with open(filename, 'r') as f:
            data = f.read()
        return int(data)
    except FileNotFoundError as e:
        raise ValueError(f"无法处理文件 {filename}") from e
    except ValueError as e:
        raise ValueError(f"文件内容无效: {filename}") from e

try:
    process_file("nonexistent.txt")
except ValueError as e:
    print(f"错误: {e}")
    print(f"原因: {e.__cause__}")

# 输出:
# 错误: 无法处理文件 nonexistent.txt
# 原因: [Errno 2] No such file or directory: 'nonexistent.txt'

抑制异常链

python 复制代码
# 使用 from None 抑制异常链
def convert_to_int(value):
    try:
        return int(value)
    except ValueError as e:
        raise TypeError("必须是数字字符串") from None

try:
    convert_to_int("abc")
except TypeError as e:
    print(e)  # 必须是数字字符串(不显示原始异常)

上下文管理器

with 语句

with 语句自动管理资源的获取和释放,即使发生异常也能正确清理。

python 复制代码
# 文件操作
with open("test.txt", "w") as f:
    f.write("Hello, World!")
# 文件自动关闭,即使发生异常

# 多个上下文管理器
with open("input.txt", "r") as infile, open("output.txt", "w") as outfile:
    data = infile.read()
    outfile.write(data.upper())

创建自定义上下文管理器

方法1:类实现
python 复制代码
class ManagedResource:
    """自定义上下文管理器"""

    def __init__(self, name):
        self.name = name

    def __enter__(self):
        """进入上下文时调用"""
        print(f"打开资源: {self.name}")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """退出上下文时调用"""
        print(f"关闭资源: {self.name}")

        # 如果返回 True,异常会被抑制
        # 如果返回 False 或 None,异常会继续传播
        if exc_type is not None:
            print(f"发生异常: {exc_type.__name__}: {exc_val}")

        return False  # 不抑制异常

# 使用
with ManagedResource("数据库连接") as resource:
    print("使用资源")
    # raise ValueError("测试异常")

# 输出:
# 打开资源: 数据库连接
# 使用资源
# 关闭资源: 数据库连接
方法2:生成器实现
python 复制代码
from contextlib import contextmanager

@contextmanager
def managed_resource(name):
    """使用生成器创建上下文管理器"""
    print(f"打开资源: {name}")
    try:
        yield name
    finally:
        print(f"关闭资源: {name}")

# 使用
with managed_resource("网络连接") as resource:
    print(f"使用资源: {resource}")

实际应用:计时器

python 复制代码
import time
from contextlib import contextmanager

@contextmanager
def timer(description="操作"):
    """计时代码执行时间"""
    start = time.time()
    try:
        yield
    finally:
        end = time.time()
        elapsed = end - start
        print(f"{description} 耗时: {elapsed:.4f} 秒")

# 使用
with timer("数据处理"):
    time.sleep(1)  # 模拟耗时操作
    result = sum(range(1000000))

with timer("文件读取"):
    with open("/etc/hosts", "r") as f:
        content = f.read()

实际应用:临时目录

python 复制代码
import os
import shutil
import tempfile
from contextlib import contextmanager

@contextmanager
def temporary_directory():
    """创建临时目录,使用后自动删除"""
    temp_dir = tempfile.mkdtemp()
    try:
        yield temp_dir
    finally:
        shutil.rmtree(temp_dir)
        print(f"临时目录 {temp_dir} 已删除")

# 使用
with temporary_directory() as temp_dir:
    temp_file = os.path.join(temp_dir, "test.txt")
    with open(temp_file, "w") as f:
        f.write("临时数据")
    print(f"临时文件: {temp_file}")
    print(f"文件存在: {os.path.exists(temp_file)}")

print(f"退出后文件存在: {os.path.exists(temp_file)}")  # False

日志记录

基本日志记录

python 复制代码
import logging

# 配置日志
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

logger = logging.getLogger(__name__)

def process_data(data):
    logger.info(f"开始处理数据: {data}")
    try:
        result = int(data) * 2
        logger.debug(f"处理结果: {result}")
        return result
    except ValueError as e:
        logger.error(f"数据处理失败: {e}", exc_info=True)
        return None

process_data("10")   # 正常
process_data("abc")  # 异常

日志级别

python 复制代码
import logging

logger = logging.getLogger(__name__)

# 日志级别(从低到高)
logger.debug("调试信息")      # 详细信息,用于调试
logger.info("一般信息")       # 确认一切正常
logger.warning("警告信息")    # 意外情况,但程序仍能运行
logger.error("错误信息")      # 严重问题,某些功能无法执行
logger.critical("严重错误")   # 程序可能无法继续运行

记录异常堆栈

python 复制代码
import logging

logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger(__name__)

def risky_operation():
    try:
        result = 1 / 0
    except ZeroDivisionError:
        # 方法1:记录异常信息
        logger.exception("发生除零错误")  # 自动包含堆栈跟踪

        # 方法2:手动指定
        # logger.error("发生除零错误", exc_info=True)

risky_operation()

# 输出包含完整的堆栈跟踪信息

日志到文件

python 复制代码
import logging

# 创建 logger
logger = logging.getLogger("app_logger")
logger.setLevel(logging.DEBUG)

# 文件处理器
file_handler = logging.FileHandler("app.log")
file_handler.setLevel(logging.WARNING)

# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# 格式化器
formatter = logging.Formatter(
    '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# 添加处理器
logger.addHandler(file_handler)
logger.addHandler(console_handler)

# 使用
logger.info("这是一般信息")
logger.warning("这是警告信息")
logger.error("这是错误信息")

最佳实践

1. 精确捕获异常

python 复制代码
# ❌ 不推荐:捕获所有异常
try:
    do_something()
except:
    pass

# ❌ 不推荐:捕获过于宽泛
try:
    do_something()
except Exception:
    pass

# ✅ 推荐:捕获特定异常
try:
    do_something()
except (ValueError, TypeError) as e:
    handle_error(e)

2. 不要静默异常

python 复制代码
# ❌ 危险:静默异常
try:
    save_data()
except:
    pass  # 错误被吞掉,难以调试

# ✅ 推荐:至少记录日志
try:
    save_data()
except IOError as e:
    logging.error(f"保存数据失败: {e}")
    raise  # 或者采取其他措施

3. 清理资源使用 finally 或 with

python 复制代码
# ❌ 不推荐:手动管理资源
file = open("data.txt")
try:
    data = file.read()
finally:
    file.close()

# ✅ 推荐:使用 with 语句
with open("data.txt") as file:
    data = file.read()

4. 使用有意义的异常消息

python 复制代码
# ❌ 不推荐:模糊的错误消息
raise ValueError("错误")

# ✅ 推荐:详细的错误消息
raise ValueError(f"用户年龄必须在 0-150 之间,收到: {age}")

5. 不要过度使用异常

python 复制代码
# ❌ 不推荐:用异常控制流程
def find_item(items, target):
    for item in items:
        try:
            if item == target:
                return item
        except:
            continue
    return None

# ✅ 推荐:正常的条件判断
def find_item(items, target):
    for item in items:
        if item == target:
            return item
    return None

6. EAFP vs LBYL

python 复制代码
# EAFP (Easier to Ask Forgiveness than Permission)
# 先尝试,出错再处理(Python 风格)
try:
    value = my_dict[key]
except KeyError:
    value = default_value

# LBYL (Look Before You Leap)
# 先检查,再执行
if key in my_dict:
    value = my_dict[key]
else:
    value = default_value

# Python 更推崇 EAFP 风格

7. 自定义异常的最佳实践

python 复制代码
# ✅ 推荐:创建异常层次结构
class MyAppError(Exception):
    """应用基础异常"""
    def __init__(self, message, code=None):
        super().__init__(message)
        self.code = code

class ValidationError(MyAppError):
    """验证错误"""
    pass

class AuthenticationError(MyAppError):
    """认证错误"""
    pass

# 使用
try:
    authenticate_user(token)
except AuthenticationError as e:
    log_security_event(e)
    raise

综合实战

实战1: 健壮的文件处理器

python 复制代码
import os
import logging
from pathlib import Path
from contextlib import contextmanager

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class FileProcessorError(Exception):
    """文件处理错误基类"""
    pass

class FileNotFoundError(FileProcessorError):
    """文件不存在"""
    pass

class FileReadError(FileProcessorError):
    """文件读取错误"""
    pass

class FileWriteError(FileProcessorError):
    """文件写入错误"""
    pass

class FileProcessor:
    """健壮的文件处理器"""

    def __init__(self, base_dir="."):
        self.base_dir = Path(base_dir)

    @contextmanager
    def safe_open(self, filepath, mode='r'):
        """安全打开文件"""
        full_path = self.base_dir / filepath

        if mode == 'r' and not full_path.exists():
            raise FileNotFoundError(f"文件不存在: {full_path}")

        file_obj = None
        try:
            file_obj = open(full_path, mode, encoding='utf-8')
            yield file_obj
        except IOError as e:
            raise FileReadError(f"文件操作失败: {full_path}, 错误: {e}") from e
        finally:
            if file_obj:
                file_obj.close()
                logger.debug(f"文件已关闭: {full_path}")

    def read_file(self, filepath):
        """读取文件内容"""
        try:
            with self.safe_open(filepath, 'r') as f:
                content = f.read()
                logger.info(f"成功读取文件: {filepath}")
                return content
        except FileNotFoundError:
            logger.warning(f"文件不存在: {filepath}")
            return None
        except FileReadError as e:
            logger.error(f"读取文件失败: {e}")
            return None

    def write_file(self, filepath, content):
        """写入文件内容"""
        try:
            # 确保目录存在
            full_path = self.base_dir / filepath
            full_path.parent.mkdir(parents=True, exist_ok=True)

            with self.safe_open(filepath, 'w') as f:
                f.write(content)
                logger.info(f"成功写入文件: {filepath}")
                return True
        except FileWriteError as e:
            logger.error(f"写入文件失败: {e}")
            return False

    def append_file(self, filepath, content):
        """追加文件内容"""
        try:
            with self.safe_open(filepath, 'a') as f:
                f.write(content)
                logger.info(f"成功追加到文件: {filepath}")
                return True
        except FileWriteError as e:
            logger.error(f"追加文件失败: {e}")
            return False

    def process_lines(self, filepath, callback):
        """逐行处理文件"""
        results = []
        line_num = 0

        try:
            with self.safe_open(filepath, 'r') as f:
                for line in f:
                    line_num += 1
                    try:
                        result = callback(line.strip(), line_num)
                        results.append(result)
                    except Exception as e:
                        logger.warning(f"处理第 {line_num} 行失败: {e}")
                        results.append(None)

            logger.info(f"处理完成: {filepath}, 共 {line_num} 行")
            return results

        except FileNotFoundError:
            logger.error(f"文件不存在: {filepath}")
            return []
        except FileReadError as e:
            logger.error(f"读取文件失败: {e}")
            return []

# 使用示例
def main():
    processor = FileProcessor("./data")

    # 写入文件
    processor.write_file("test.txt", "Hello\nWorld\nPython\n")

    # 读取文件
    content = processor.read_file("test.txt")
    if content:
        print(f"文件内容:\n{content}")

    # 逐行处理
    def uppercase_line(line, line_num):
        return f"第{line_num}行: {line.upper()}"

    results = processor.process_lines("test.txt", uppercase_line)
    for result in results:
        if result:
            print(result)

    # 处理不存在的文件
    content = processor.read_file("nonexistent.txt")
    print(f"不存在的文件: {content}")

if __name__ == "__main__":
    main()

实战2: API 请求封装

python 复制代码
import requests
import time
import logging
from functools import wraps

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class APIError(Exception):
    """API 错误基类"""
    def __init__(self, message, status_code=None, response=None):
        super().__init__(message)
        self.status_code = status_code
        self.response = response

class NetworkError(APIError):
    """网络错误"""
    pass

class AuthenticationError(APIError):
    """认证错误"""
    pass

class RateLimitError(APIError):
    """速率限制错误"""
    def __init__(self, message, retry_after=None):
        super().__init__(message)
        self.retry_after = retry_after

class NotFoundError(APIError):
    """资源不存在"""
    pass

def retry_on_failure(max_retries=3, delay=1, backoff=2):
    """重试装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None

            for attempt in range(1, max_retries + 1):
                try:
                    return func(*args, **kwargs)
                except (NetworkError, RateLimitError) as e:
                    last_exception = e

                    if attempt < max_retries:
                        wait_time = delay * (backoff ** (attempt - 1))
                        logger.warning(
                            f"尝试 {attempt}/{max_retries} 失败: {e}. "
                            f"{wait_time} 秒后重试..."
                        )
                        time.sleep(wait_time)
                    else:
                        logger.error(f"所有 {max_retries} 次尝试都失败了")

            raise last_exception
        return wrapper
    return decorator

class APIClient:
    """健壮的 API 客户端"""

    def __init__(self, base_url, api_key=None, timeout=10):
        self.base_url = base_url.rstrip('/')
        self.api_key = api_key
        self.timeout = timeout
        self.session = requests.Session()

        if api_key:
            self.session.headers.update({
                'Authorization': f'Bearer {api_key}'
            })

    def _build_url(self, endpoint):
        """构建完整 URL"""
        return f"{self.base_url}/{endpoint.lstrip('/')}"

    def _handle_response(self, response):
        """处理响应"""
        if response.status_code == 200:
            return response.json()
        elif response.status_code == 401:
            raise AuthenticationError(
                "认证失败",
                status_code=401,
                response=response
            )
        elif response.status_code == 404:
            raise NotFoundError(
                f"资源不存在: {response.url}",
                status_code=404,
                response=response
            )
        elif response.status_code == 429:
            retry_after = response.headers.get('Retry-After', 1)
            raise RateLimitError(
                "请求过于频繁",
                retry_after=int(retry_after)
            )
        elif response.status_code >= 500:
            raise NetworkError(
                f"服务器错误: {response.status_code}",
                status_code=response.status_code,
                response=response
            )
        else:
            raise APIError(
                f"HTTP {response.status_code}: {response.text}",
                status_code=response.status_code,
                response=response
            )

    @retry_on_failure(max_retries=3, delay=1, backoff=2)
    def get(self, endpoint, params=None):
        """GET 请求"""
        url = self._build_url(endpoint)

        try:
            logger.info(f"GET {url}")
            response = self.session.get(
                url,
                params=params,
                timeout=self.timeout
            )
            response.raise_for_status()
            return self._handle_response(response)

        except requests.exceptions.ConnectionError as e:
            raise NetworkError(f"连接失败: {e}") from e
        except requests.exceptions.Timeout as e:
            raise NetworkError(f"请求超时: {e}") from e
        except requests.exceptions.RequestException as e:
            raise APIError(f"请求失败: {e}") from e

    @retry_on_failure(max_retries=3, delay=1, backoff=2)
    def post(self, endpoint, data=None, json=None):
        """POST 请求"""
        url = self._build_url(endpoint)

        try:
            logger.info(f"POST {url}")
            response = self.session.post(
                url,
                data=data,
                json=json,
                timeout=self.timeout
            )
            response.raise_for_status()
            return self._handle_response(response)

        except requests.exceptions.RequestException as e:
            raise APIError(f"POST 请求失败: {e}") from e

    def close(self):
        """关闭会话"""
        self.session.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

# 使用示例
def main():
    # 使用 JSONPlaceholder 测试 API
    with APIClient("https://jsonplaceholder.typicode.com") as client:
        try:
            # 获取帖子
            posts = client.get("/posts", params={"_limit": 5})
            print(f"获取到 {len(posts)} 个帖子")

            for post in posts[:2]:
                print(f"  - {post['title'][:50]}...")

            # 获取单个帖子
            post = client.get("/posts/1")
            print(f"\n帖子标题: {post['title']}")

            # 尝试获取不存在的资源
            try:
                client.get("/posts/999999")
            except NotFoundError as e:
                print(f"\n预期错误: {e}")

            # 发布新帖子
            new_post = {
                "title": "Test Post",
                "body": "This is a test",
                "userId": 1
            }
            result = client.post("/posts", json=new_post)
            print(f"\n创建帖子 ID: {result['id']}")

        except AuthenticationError as e:
            print(f"认证错误: {e}")
        except NetworkError as e:
            print(f"网络错误: {e}")
        except APIError as e:
            print(f"API 错误: {e}")

if __name__ == "__main__":
    main()

实战3: 数据验证框架

python 复制代码
from typing import Any, Dict, List, Optional
from dataclasses import dataclass, field
import re
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@dataclass
class ValidationError:
    """验证错误"""
    field: str
    message: str
    value: Any = None

    def __str__(self):
        return f"{self.field}: {self.message} (值: {self.value})"

@dataclass
class ValidationResult:
    """验证结果"""
    is_valid: bool = True
    errors: List[ValidationError] = field(default_factory=list)

    def add_error(self, field: str, message: str, value: Any = None):
        self.is_valid = False
        self.errors.append(ValidationError(field, message, value))

    def merge(self, other: 'ValidationResult'):
        """合并验证结果"""
        if not other.is_valid:
            self.is_valid = False
            self.errors.extend(other.errors)

    def raise_if_invalid(self):
        """如果验证失败,抛出异常"""
        if not self.is_valid:
            error_messages = "\n".join(str(e) for e in self.errors)
            raise ValueError(f"验证失败:\n{error_messages}")

class Validator:
    """验证器基类"""

    def validate(self, value: Any) -> ValidationResult:
        raise NotImplementedError

class RequiredValidator(Validator):
    """必填验证"""

    def __init__(self, message: str = "此字段为必填项"):
        self.message = message

    def validate(self, value: Any) -> ValidationResult:
        result = ValidationResult()
        if value is None or value == "":
            result.add_error("field", self.message, value)
        return result

class StringValidator(Validator):
    """字符串验证"""

    def __init__(
        self,
        min_length: int = None,
        max_length: int = None,
        pattern: str = None,
        message: str = None
    ):
        self.min_length = min_length
        self.max_length = max_length
        self.pattern = re.compile(pattern) if pattern else None
        self.message = message or "字符串验证失败"

    def validate(self, value: Any) -> ValidationResult:
        result = ValidationResult()

        if not isinstance(value, str):
            result.add_error("field", "必须是字符串", value)
            return result

        if self.min_length and len(value) < self.min_length:
            result.add_error(
                "field",
                f"长度不能少于 {self.min_length} 个字符",
                value
            )

        if self.max_length and len(value) > self.max_length:
            result.add_error(
                "field",
                f"长度不能超过 {self.max_length} 个字符",
                value
            )

        if self.pattern and not self.pattern.match(value):
            result.add_error("field", self.message, value)

        return result

class NumberValidator(Validator):
    """数字验证"""

    def __init__(
        self,
        min_value: float = None,
        max_value: float = None,
        message: str = None
    ):
        self.min_value = min_value
        self.max_value = max_value
        self.message = message or "数字验证失败"

    def validate(self, value: Any) -> ValidationResult:
        result = ValidationResult()

        if not isinstance(value, (int, float)):
            result.add_error("field", "必须是数字", value)
            return result

        if self.min_value is not None and value < self.min_value:
            result.add_error(
                "field",
                f"不能小于 {self.min_value}",
                value
            )

        if self.max_value is not None and value > self.max_value:
            result.add_error(
                "field",
                f"不能大于 {self.max_value}",
                value
            )

        return result

class EmailValidator(Validator):
    """邮箱验证"""

    def __init__(self, message: str = "邮箱格式不正确"):
        self.pattern = re.compile(
            r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        )
        self.message = message

    def validate(self, value: Any) -> ValidationResult:
        result = ValidationResult()

        if not isinstance(value, str):
            result.add_error("field", "必须是字符串", value)
            return result

        if not self.pattern.match(value):
            result.add_error("field", self.message, value)

        return result

class FieldValidator:
    """字段验证器"""

    def __init__(self, field_name: str):
        self.field_name = field_name
        self.validators: List[Validator] = []

    def add_validator(self, validator: Validator):
        self.validators.append(validator)
        return self

    def required(self, message: str = "此字段为必填项"):
        return self.add_validator(RequiredValidator(message))

    def string(self, min_length=None, max_length=None, pattern=None, message=None):
        return self.add_validator(
            StringValidator(min_length, max_length, pattern, message)
        )

    def number(self, min_value=None, max_value=None, message=None):
        return self.add_validator(
            NumberValidator(min_value, max_value, message)
        )

    def email(self, message: str = "邮箱格式不正确"):
        return self.add_validator(EmailValidator(message))

    def validate(self, value: Any) -> ValidationResult:
        result = ValidationResult()

        for validator in self.validators:
            field_result = validator.validate(value)

            # 更新错误信息的字段名
            for error in field_result.errors:
                error.field = self.field_name

            result.merge(field_result)

            # 如果当前验证失败,跳过后续验证
            if not field_result.is_valid:
                break

        return result

class SchemaValidator:
    """模式验证器"""

    def __init__(self):
        self.fields: Dict[str, FieldValidator] = {}

    def field(self, field_name: str) -> FieldValidator:
        """定义字段验证器"""
        if field_name not in self.fields:
            self.fields[field_name] = FieldValidator(field_name)
        return self.fields[field_name]

    def validate(self, data: Dict[str, Any]) -> ValidationResult:
        """验证数据"""
        result = ValidationResult()

        for field_name, field_validator in self.fields.items():
            value = data.get(field_name)
            field_result = field_validator.validate(value)
            result.merge(field_result)

        return result

# 使用示例
def main():
    # 定义用户注册验证模式
    user_schema = SchemaValidator()

    user_schema.field("username") \
        .required("用户名不能为空") \
        .string(min_length=3, max_length=20, message="用户名只能包含字母、数字和下划线")

    user_schema.field("email") \
        .required("邮箱不能为空") \
        .email("邮箱格式不正确")

    user_schema.field("age") \
        .required("年龄不能为空") \
        .number(min_value=0, max_value=150, message="年龄必须在 0-150 之间")

    user_schema.field("password") \
        .required("密码不能为空") \
        .string(min_length=8, message="密码至少8个字符")

    # 测试数据
    test_cases = [
        {
            "username": "john",
            "email": "john@example.com",
            "age": 25,
            "password": "Password123"
        },
        {
            "username": "ab",  # 太短
            "email": "invalid",  # 格式错误
            "age": -5,  # 超出范围
            "password": "123"  # 太短
        },
        {
            "username": "",
            "email": "",
            "age": None,
            "password": ""
        }
    ]

    for i, data in enumerate(test_cases, 1):
        print(f"\n{'='*60}")
        print(f"测试用例 {i}:")
        print(f"{'='*60}")

        result = user_schema.validate(data)

        if result.is_valid:
            print("✓ 验证通过")
        else:
            print("✗ 验证失败:")
            for error in result.errors:
                print(f"  - {error}")

if __name__ == "__main__":
    main()

小结

概念 说明 使用场景
try-except 捕获和处理异常 处理可预见的错误
finally 无论如何都执行 清理资源
else 无异常时执行 区分正常和异常流程
raise 抛出异常 报告错误
自定义异常 创建特定异常类 业务逻辑错误
异常链 保留原始异常 异常转换时保留上下文
上下文管理器 自动资源管理 文件、数据库连接等
日志记录 记录错误信息 调试和监控

核心要点

  • 异常处理让程序更健壮
  • 精确捕获,不要吞掉异常
  • 使用 finally 或 with 清理资源
  • 创建有意义的自定义异常
  • 合理使用日志记录错误
  • EAFP 风格更符合 Python 哲学
  • 不要滥用异常控制流程

掌握异常处理是编写高质量 Python 代码的关键!

相关推荐
7年前端辞职转AI2 小时前
Python 面向对象编程
python·编程语言
kishu_iOS&AI2 小时前
机器学习 —— 总结
人工智能·python·机器学习·线性回归
API快乐传递者2 小时前
Python 爬虫获取 1688 商品详情 API 接口实战指南
java·前端·python
疯狂成瘾者2 小时前
LangChain Middleware 技术解析:从“插槽机制”到 Agent 运行时控制
数据库·python·langchain
Where-2 小时前
LangChain、LangGraph入门
python·langchain·langgraph
极光代码工作室2 小时前
基于机器学习的垃圾短信识别系统
人工智能·python·深度学习·机器学习
2201_756847332 小时前
如何设置备库只接日志不应用_暂停MRP且维持网络传输的方法
jvm·数据库·python
zhaoshuzhaoshu2 小时前
Python 语法之控制结构详解
开发语言·python
xcbrand2 小时前
工业制造品牌全案公司找哪家
大数据·人工智能·python·制造