python Web开发从入门到精通(二)别死记语法!用实际案例理解Python基础

4 阅读1分钟

摘要钩子

你是不是也曾经:对着Python语法书看了半天,每个单词都认识,但合上书却写不出几行代码?面对if-else、for循环这些基础概念,感觉理解了,但一到实际项目就卡壳?

别担心,这不是你的问题!传统学习方式最大的误区就是脱离实际场景死记语法。今天,我将用5个真实的开发案例,带你彻底理解Python基础,让你不再死记硬背,而是真正掌握编程思维。

学完本篇,你将能够:

  1. 用实际项目需求驱动学习,告别枯燥的语法背诵
  2. 理解变量、流程控制、循环、函数、异常处理的实际应用场景
  3. 写出可运行、可复用的Python代码,解决真实问题
  4. 建立完整的Python基础思维导图,知识不再零散

一、开场:从"能看懂"到"会写代码"的转变

很多Python初学者都有这样的经历:看了很多教程,理解了每个概念,但一到自己动手就大脑空白。问题出在哪里?

根本原因:学习方式错了!

传统学习路径:语法概念 → 简单示例 → 记忆背诵 → 考试通过

实际开发路径:项目需求 → 分析问题 → 选择工具 → 编写代码 → 测试验证

今天,我们就切换到这个实战开发路径,通过5个真实案例,让你真正学会Python基础。

案例地图

为了让你有个整体概念,先看看我们今天要解决的5个实际问题:

案例

涉及知识点

实际应用场景

1. 用户注册信息验证

变量、数据类型、条件判断

网站注册、表单验证

2. 文件批量处理工具

字符串操作、列表、循环

办公自动化、数据整理

3. 电商商品类设计

面向对象基础、自定义异常

电商系统开发

4. 员工信息管理系统

函数、四种参数传递方式

企业内部管理系统

5. 网络请求错误处理

异常处理、自定义异常

API接口开发

看到没?每一个案例都是你未来工作中可能遇到的真实需求!现在就让我们开始第一个案例。

二、案例1:用户注册信息验证系统

问题场景

假设你要为一个网站开发用户注册功能,需要验证用户输入的信息是否符合要求:

  1. 用户名:6-12位字符,不能包含空格
  2. 密码:至少8位,包含大小写字母和数字
  3. 邮箱:必须包含且仅包含1个@符号,@前后都有内容

传统写法 vs 实战写法

传统写法(教科书式)

python

# 传统分离写法
def validate_username(username):
    if len(username) < 6:
        return False
    elif len(username) > 12:
        return False
    elif ' ' in username:
        return False
    else:
        return True

这种写法虽然正确,但有几个问题:

  1. 错误信息不明确,用户不知道具体哪里错了
  2. 代码重复,每个字段都要写相似的验证逻辑
  3. 扩展性差,加一个新验证规则要改很多地方

实战写法(工程思维)

python

# 实战:使用Pydantic进行数据验证
from pydantic import BaseModel, Field, ValidationError, field_validator
import re

class UserRegisterData(BaseModel):
    """用户注册数据模型"""
    username: str = Field(
        min_length=6,
        max_length=12,
        description="用户名,6-12位字符,不能包含空格"
    )
    password: str
    email: str
    
    @field_validator('username')
    def validate_username(cls, value):
        """验证用户名"""
        if ' ' in value:
            raise ValueError('用户名不能包含空格')
        return value
    
    @field_validator('password')
    def validate_password(cls, value):
        """验证密码强度"""
        if len(value) < 8:
            raise ValueError('密码长度至少8位')
        if not any(c.isupper() for c in value):
            raise ValueError('密码必须包含至少1个大写字母')
        if not any(c.islower() for c in value):
            raise ValueError('密码必须包含至少1个小写字母')
        if not any(c.isdigit() for c in value):
            raise ValueError('密码必须包含至少1个数字')
        return value
    
    @field_validator('email')
    def validate_email(cls, value):
        """验证邮箱格式"""
        if value.count('@') != 1:
            raise ValueError('邮箱必须包含且仅包含1个@符号')
        parts = value.split('@')
        if not parts[0] or not parts[1]:
            raise ValueError('@符号前后必须有内容')
        return value

# 使用示例
def register_user(username: str, password: str, email: str):
    """用户注册函数"""
    try:
        # 数据验证(自动触发所有验证器)
        user_data = UserRegisterData(
            username=username,
            password=password,
            email=email
        )
        print(f"✅ 用户 {user_data.username} 验证通过!")
        
        # 这里可以继续业务逻辑:保存到数据库等
        return {"status": "success", "user": user_data}
        
    except ValidationError as e:
        # 收集所有错误信息
        errors = []
        for error in e.errors():
            field = error['loc'][0] if error['loc'] else '未知字段'
            msg = error['msg']
            errors.append(f"{field}: {msg}")
        
        print("❌ 验证失败:")
        for error in errors:
            print(f"  - {error}")
        
        return {"status": "error", "errors": errors}

# 测试不同场景
if __name__ == "__main__":
    print("=== 测试1:正常数据 ===")
    register_user("john_doe123", "SecurePass123", "john@example.com")
    
    print("\n=== 测试2:用户名过短 ===")
    register_user("john", "SecurePass123", "john@example.com")
    
    print("\n=== 测试3:密码强度不足 ===")
    register_user("john_doe123", "weak", "john@example.com")
    
    print("\n=== 测试4:邮箱格式错误 ===")
    register_user("john_doe123", "SecurePass123", "invalid-email")

知识点解析:变量与数据类型

在这个案例中,我们用到了Python的核心基础:

1. 变量定义

username: str = Field(...)
  • username 是变量名
  • : str 是类型注解,表示这个变量应该是字符串类型
  • = Field(...) 是默认值和验证规则的结合

2. 数据类型

  • 字符串(str)username, password, email 都是字符串
  • 布尔(bool) :验证函数返回的True/False
  • 列表(list)errors 是一个列表,存储多个错误信息

3. 字符串操作

if ' ' in value:  # 检查是否包含空格
if value.count('@') != 1:  # 统计@符号数量
parts = value.split('@')  # 分割字符串
any(c.isupper() for c in value)  # 检查是否有大写字母

实战技巧:为什么用Pydantic?

你可能想问:为什么不用简单的if-else,而要引入Pydantic?

原因一:错误信息更友好

  • 传统方式:只能返回一个错误,用户要逐个试错
  • Pydantic:一次返回所有错误,用户一次性修正

原因二:代码复用性

  • 传统方式:每个验证逻辑都要重写
  • Pydantic:定义一次模型,到处使用

原因三:类型安全

  • Python是动态类型,容易传错参数
  • Pydantic在运行时检查类型,提前发现错误

动手练习

现在,请你在自己的电脑上运行上面的代码。注意:你需要先安装Pydantic:

pip install pydantic

然后修改测试数据,看看不同的输入会产生什么验证结果。这是理解编程最好的方式——动手实践!

三、案例2:文件批量处理工具

问题场景

假设你是一家公司的数据分析师,每周都会收到上百个Excel数据文件,需要:

  1. 将所有的.xlsx文件转换为.csv格式
  2. 批量重命名文件,添加日期前缀
  3. 统计每个文件的数据行数,生成报告

实战代码

# 文件批量处理工具
import os
import shutil
from pathlib import Path
import pandas as pd
from datetime import datetime
import glob

class FileBatchProcessor:
    """文件批量处理器"""
    
    def __init__(self, source_dir: str, target_dir: str):
        self.source_dir = Path(source_dir)
        self.target_dir = Path(target_dir)
        self.target_dir.mkdir(parents=True, exist_ok=True)
        
    def get_all_files(self, pattern: str = "*.*"):
        """获取所有匹配的文件"""
        files = list(self.source_dir.glob(pattern))
        return sorted(files)
    
    def convert_xlsx_to_csv(self):
        """批量转换xlsx为csv"""
        xlsx_files = self.get_all_files("*.xlsx")
        results = []
        
        for xlsx_file in xlsx_files:
            try:
                # 读取Excel文件
                df = pd.read_excel(xlsx_file)
                
                # 生成新的文件名
                csv_file = self.target_dir / f"{xlsx_file.stem}.csv"
                
                # 保存为CSV
                df.to_csv(csv_file, index=False, encoding='utf-8')
                
                # 记录结果
                results.append({
                    '原文件': xlsx_file.name,
                    '新文件': csv_file.name,
                    '数据行数': len(df),
                    '列数': len(df.columns),
                    '状态': '成功'
                })
                
                print(f"✅ 转换完成:{xlsx_file.name}{csv_file.name} "
                      f"({len(df)}行×{len(df.columns)}列)")
                      
            except Exception as e:
                results.append({
                    '原文件': xlsx_file.name,
                    '错误': str(e),
                    '状态': '失败'
                })
                print(f"❌ 转换失败:{xlsx_file.name} - {e}")
        
        return results
    
    def batch_rename_with_date(self):
        """批量添加日期前缀"""
        today = datetime.now().strftime("%Y%m%d")
        all_files = self.get_all_files("*.*")
        renamed_files = []
        
        for file in all_files:
            if file.is_file():
                # 构建新文件名:日期_原文件名
                new_name = f"{today}_{file.name}"
                new_path = self.target_dir / new_name
                
                # 复制文件到新位置
                shutil.copy2(file, new_path)
                
                renamed_files.append({
                    '原文件': file.name,
                    '新文件': new_name,
                    '路径': str(new_path)
                })
                
                print(f"📝 重命名:{file.name}{new_name}")
        
        return renamed_files
    
    def generate_file_report(self):
        """生成文件统计报告"""
        all_files = self.get_all_files("*.*")
        report = []
        
        for file in all_files:
            if file.is_file():
                # 获取文件信息
                size_kb = file.stat().st_size / 1024
                suffix = file.suffix.lower()
                
                # 统计行数(如果是文本文件)
                line_count = 0
                if suffix in ['.txt', '.csv', '.py', '.md']:
                    try:
                        with open(file, 'r', encoding='utf-8') as f:
                            line_count = sum(1 for _ in f)
                    except:
                        line_count = -1  # 无法读取
                
                report.append({
                    '文件名': file.name,
                    '类型': suffix[1:] if suffix else '无扩展名',
                    '大小(KB)': round(size_kb, 2),
                    '行数': line_count,
                    '修改时间': datetime.fromtimestamp(file.stat().st_mtime).strftime('%Y-%m-%d %H:%M'),
                    '完整路径': str(file)
                })
        
        return report
    
    def run_all_tasks(self):
        """执行所有批量任务"""
        print("=" * 50)
        print("开始批量文件处理...")
        print(f"源目录: {self.source_dir}")
        print(f"目标目录: {self.target_dir}")
        print("=" * 50)
        
        # 执行各项任务
        conversion_results = self.convert_xlsx_to_csv()
        rename_results = self.batch_rename_with_date()
        report_results = self.generate_file_report()
        
        # 生成汇总报告
        summary = {
            '转换文件数': len([r for r in conversion_results if r['状态'] == '成功']),
            '重命名文件数': len(rename_results),
            '总处理文件数': len(report_results),
            '成功': len([r for r in conversion_results if r['状态'] == '成功']),
            '失败': len([r for r in conversion_results if r['状态'] == '失败'])
        }
        
        print("\n" + "=" * 50)
        print("批量处理完成!")
        print(f"✅ 成功转换文件: {summary['成功']}个")
        print(f"📝 重命名文件: {summary['重命名文件数']}个")
        print(f"📊 总处理文件: {summary['总处理文件数']}个")
        print("=" * 50)
        
        return {
            'conversion': conversion_results,
            'rename': rename_results,
            'report': report_results,
            'summary': summary
        }

# 使用示例
if __name__ == "__main__":
    # 设置目录(请根据实际情况修改)
    source_directory = "data/source_files"  # 源文件目录
    target_directory = "data/processed_files"  # 处理后的文件目录
    
    # 如果目录不存在,创建示例文件
    source_path = Path(source_directory)
    if not source_path.exists():
        source_path.mkdir(parents=True, exist_ok=True)
        print("创建示例文件...")
        
        # 创建几个示例Excel文件
        for i in range(1, 4):
            df = pd.DataFrame({
                'ID': range(1, 101),
                'Name': [f'用户_{j}' for j in range(1, 101)],
                'Score': [j * 10 for j in range(1, 101)]
            })
            df.to_excel(source_path / f"data_{i}.xlsx", index=False)
        
        # 创建几个文本文件
        for i in range(1, 3):
            with open(source_path / f"log_{i}.txt", 'w', encoding='utf-8') as f:
                f.write(f"这是第{i}个日志文件\n" * 10)
    
    # 执行批量处理
    processor = FileBatchProcessor(source_directory, target_directory)
    results = processor.run_all_tasks()
    
    # 保存报告到CSV
    report_df = pd.DataFrame(results['report'])
    report_file = Path(target_directory) / "file_report.csv"
    report_df.to_csv(report_file, index=False)
    
    print(f"\n📋 详细报告已保存到: {report_file}")

知识点解析:列表与循环

1. 列表操作

# 获取所有文件
files = list(self.source_dir.glob(pattern))
return sorted(files)  # 排序

# 遍历列表
for file in all_files:
    if file.is_file():
        # 处理每个文件

2. 循环结构

  • for循环:遍历文件列表,处理每个文件
  • while循环:在这个案例中没有用到,适合不确定循环次数的场景

3. 条件判断

if file.is_file():  # 检查是否是文件
if suffix in ['.txt', '.csv', '.py', '.md']:  # 检查文件类型

4. 文件操作

from pathlib import Path  # 现代文件路径操作
import shutil  # 高级文件操作
import pandas as pd  # 数据处理

性能考量:for循环 vs 列表推导式

在处理大量文件时,性能很重要:

# 传统for循环
results = []
for file in files:
    results.append(process_file(file))

# 列表推导式(更简洁,性能略好)
results = [process_file(file) for file in files]

# 生成器表达式(内存效率高,适合大文件)
results = (process_file(file) for file in files)

动手练习

  1. 创建一个test_files目录,放入不同类型的文件(.txt, .csv, .xlsx等)
  2. 运行上面的代码,观察处理结果
  3. 尝试修改代码,增加新的功能:比如只处理最近7天修改过的文件

四、案例3:电商商品类设计

问题场景

现在我们要设计一个电商系统的商品模块,需要:

  1. 定义商品类,包含基本属性
  2. 实现商品库存管理
  3. 处理商品上下架状态
  4. 自定义商品相关的异常

实战代码

# 电商商品类设计
from enum import Enum
from typing import List, Optional
from datetime import datetime
import uuid

# 枚举类型:商品状态
class ProductStatus(Enum):
    DRAFT = "草稿"  # 草稿状态
    PUBLISHED = "已上架"  # 已上架
    SOLD_OUT = "售罄"  # 售罄
    DISCONTINUED = "已下架"  # 已下架

# 自定义异常
class ProductError(Exception):
    """商品相关异常基类"""
    pass

class InsufficientStockError(ProductError):
    """库存不足异常"""
    def __init__(self, product_name: str, requested: int, available: int):
        self.product_name = product_name
        self.requested = requested
        self.available = available
        super().__init__(f"商品 '{product_name}' 库存不足!需要 {requested},但只有 {available}")

class InvalidPriceError(ProductError):
    """价格无效异常"""
    def __init__(self, price: float):
        self.price = price
        super().__init__(f"价格 {price} 无效!必须大于0")

class InvalidStatusTransitionError(ProductError):
    """状态转换无效异常"""
    def __init__(self, current: ProductStatus, target: ProductStatus):
        self.current = current
        self.target = target
        super().__init__(f"不能从状态 '{current.value}' 转换到 '{target.value}'")

# 商品类
class Product:
    """电商商品类"""
    
    def __init__(
        self,
        name: str,
        price: float,
        stock: int = 0,
        description: str = "",
        category: str = "未分类"
    ):
        # 基本验证
        if price <= 0:
            raise InvalidPriceError(price)
        if stock < 0:
            raise ValueError("库存不能为负数")
        
        # 生成唯一ID
        self.id = str(uuid.uuid4())
        self.name = name
        self.price = price
        self._stock = stock  # 私有属性,通过方法访问
        self.description = description
        self.category = category
        
        # 状态和元数据
        self.status = ProductStatus.DRAFT
        self.created_at = datetime.now()
        self.updated_at = datetime.now()
        
        # 商品图片列表
        self.images: List[str] = []
        
        # 商品标签
        self.tags: List[str] = []
    
    # 属性访问器
    @property
    def stock(self) -> int:
        """获取当前库存"""
        return self._stock
    
    @stock.setter
    def stock(self, value: int):
        """设置库存(有验证)"""
        if value < 0:
            raise ValueError("库存不能为负数")
        self._stock = value
        self.updated_at = datetime.now()
    
    # 业务方法
    def increase_stock(self, amount: int) -> None:
        """增加库存"""
        if amount <= 0:
            raise ValueError("增加数量必须大于0")
        self._stock += amount
        self.updated_at = datetime.now()
        print(f"📦 商品 '{self.name}' 库存增加 {amount},当前库存: {self._stock}")
    
    def decrease_stock(self, amount: int) -> None:
        """减少库存(比如订单购买)"""
        if amount <= 0:
            raise ValueError("减少数量必须大于0")
        
        if self._stock < amount:
            raise InsufficientStockError(self.name, amount, self._stock)
        
        self._stock -= amount
        self.updated_at = datetime.now()
        
        print(f"🛒 商品 '{self.name}' 库存减少 {amount},当前库存: {self._stock}")
        
        # 如果库存为0,自动标记为售罄
        if self._stock == 0 and self.status == ProductStatus.PUBLISHED:
            self.status = ProductStatus.SOLD_OUT
            print(f"⚠️  商品 '{self.name}' 已售罄!")
    
    def publish(self) -> None:
        """发布商品(上架)"""
        if self.status == ProductStatus.PUBLISHED:
            print(f"商品 '{self.name}' 已经上架")
            return
        
        # 检查状态转换是否有效
        valid_transitions = {
            ProductStatus.DRAFT: [ProductStatus.PUBLISHED],
            ProductStatus.SOLD_OUT: [ProductStatus.PUBLISHED],
        }
        
        if self.status not in valid_transitions:
            raise InvalidStatusTransitionError(self.status, ProductStatus.PUBLISHED)
        
        if ProductStatus.PUBLISHED not in valid_transitions[self.status]:
            raise InvalidStatusTransitionError(self.status, ProductStatus.PUBLISHED)
        
        # 发布商品
        self.status = ProductStatus.PUBLISHED
        self.updated_at = datetime.now()
        
        print(f"✅ 商品 '{self.name}' 已成功上架!")
        print(f"   价格: ¥{self.price:.2f}")
        print(f"   库存: {self._stock}件")
        print(f"   状态: {self.status.value}")
    
    def discontinue(self) -> None:
        """下架商品"""
        if self.status == ProductStatus.DISCONTINUED:
            print(f"商品 '{self.name}' 已经下架")
            return
        
        self.status = ProductStatus.DISCONTINUED
        self.updated_at = datetime.now()
        
        print(f"⏸️  商品 '{self.name}' 已下架")
    
    def add_image(self, image_url: str) -> None:
        """添加商品图片"""
        self.images.append(image_url)
        print(f"🖼️  为商品 '{self.name}' 添加图片: {image_url}")
    
    def add_tag(self, tag: str) -> None:
        """添加商品标签"""
        if tag not in self.tags:
            self.tags.append(tag)
            print(f"🏷️  为商品 '{self.name}' 添加标签: {tag}")
    
    def get_summary(self) -> dict:
        """获取商品摘要信息"""
        return {
            'id': self.id,
            'name': self.name,
            'price': self.price,
            'stock': self._stock,
            'status': self.status.value,
            'category': self.category,
            'created_at': self.created_at.strftime('%Y-%m-%d %H:%M'),
            'updated_at': self.updated_at.strftime('%Y-%m-%d %H:%M')
        }
    
    def __str__(self) -> str:
        """字符串表示"""
        return (f"商品: {self.name} | 价格: ¥{self.price:.2f} | "
                f"库存: {self._stock} | 状态: {self.status.value}")

# 商品管理类
class ProductManager:
    """商品管理器"""
    
    def __init__(self):
        self.products: dict[str, Product] = {}  # ID -> Product
    
    def add_product(self, product: Product) -> None:
        """添加商品"""
        self.products[product.id] = product
        print(f"📝 添加商品: {product.name} (ID: {product.id})")
    
    def get_product(self, product_id: str) -> Optional[Product]:
        """根据ID获取商品"""
        return self.products.get(product_id)
    
    def list_products(self, category: Optional[str] = None) -> List[dict]:
        """列出商品(可按分类筛选)"""
        products_list = []
        
        for product in self.products.values():
            if category is None or product.category == category:
                products_list.append(product.get_summary())
        
        return products_list
    
    def restock_product(self, product_id: str, amount: int) -> None:
        """为商品补货"""
        product = self.get_product(product_id)
        if product is None:
            print(f"❌ 找不到商品 ID: {product_id}")
            return
        
        product.increase_stock(amount)
        
        # 如果之前是售罄状态,补货后自动重新上架
        if product.status == ProductStatus.SOLD_OUT:
            product.status = ProductStatus.PUBLISHED
            print(f"🔄 商品 '{product.name}' 已重新上架")
    
    def process_order(self, product_id: str, quantity: int) -> bool:
        """处理订单(减少库存)"""
        product = self.get_product(product_id)
        if product is None:
            print(f"❌ 找不到商品 ID: {product_id}")
            return False
        
        try:
            product.decrease_stock(quantity)
            return True
        except InsufficientStockError as e:
            print(f"❌ 订单处理失败: {e}")
            return False

# 使用示例
if __name__ == "__main__":
    print("=" * 50)
    print("电商商品系统演示")
    print("=" * 50)
    
    # 创建商品管理器
    manager = ProductManager()
    
    # 创建几个商品
    try:
        # iPhone商品
        iphone = Product(
            name="iPhone 15 Pro",
            price=7999.00,
            stock=50,
            description="苹果最新旗舰手机",
            category="电子产品"
        )
        iphone.add_image("https://example.com/iphone15.jpg")
        iphone.add_tag("手机")
        iphone.add_tag("苹果")
        
        # 图书商品
        book = Product(
            name="Python编程从入门到实践",
            price=89.00,
            stock=100,
            description="Python学习经典教材",
            category="图书"
        )
        book.add_tag("编程")
        book.add_tag("Python")
        
        # 错误示例:价格无效
        try:
            invalid_product = Product(name="测试商品", price=-100)
        except InvalidPriceError as e:
            print(f"预期错误: {e}")
        
    except Exception as e:
        print(f"创建商品时出错: {e}")
        exit(1)
    
    # 添加商品到管理器
    manager.add_product(iphone)
    manager.add_product(book)
    
    print("\n" + "=" * 50)
    print("商品列表:")
    print("=" * 50)
    
    # 列出所有商品
    all_products = manager.list_products()
    for product in all_products:
        print(f"• {product['name']} - ¥{product['price']:.2f} "
              f"({product['stock']}件,状态: {product['status']})")
    
    print("\n" + "=" * 50)
    print("发布商品:")
    print("=" * 50)
    
    # 发布商品
    iphone.publish()
    book.publish()
    
    print("\n" + "=" * 50)
    print("处理订单:")
    print("=" * 50)
    
    # 处理订单
    print("1. 正常订单:")
    manager.process_order(iphone.id, 2)  # 购买2台iPhone
    
    print("\n2. 库存不足订单:")
    manager.process_order(iphone.id, 100)  # 试图购买100台
    
    print("\n3. 补货后再次购买:")
    manager.restock_product(iphone.id, 60)  # 补货60台
    manager.process_order(iphone.id, 10)  # 再次购买10台
    
    print("\n" + "=" * 50)
    print("按分类筛选商品:")
    print("=" * 50)
    
    # 按分类筛选
    electronics = manager.list_products(category="电子产品")
    print(f"电子产品 ({len(electronics)}件):")
    for product in electronics:
        print(f"  - {product['name']}")
    
    print("\n" + "=" * 50)
    print("商品下架演示:")
    print("=" * 50)
    
    # 下架商品
    book.discontinue()
    
    # 查看最终状态
    print(f"\niPhone状态: {iphone}")
    print(f"图书状态: {book}")

知识点解析:面向对象基础

1. 类定义

class Product:
    def __init__(self, name: str, price: float, stock: int = 0):
        # 初始化方法
        self.name = name
        self.price = price
        self.stock = stock

2. 属性与方法

  • 实例属性self.name, self.price
  • 类属性:通过@property装饰器定义的属性
  • 实例方法increase_stock(), decrease_stock()
  • 特殊方法__str__() 用于字符串表示

3. 封装

self._stock = stock  # 私有属性(约定)

@property
def stock(self):
    return self._stock

@stock.setter
def stock(self, value):
    if value < 0:
        raise ValueError("库存不能为负数")
    self._stock = value

4. 继承与多态

  • 通过自定义异常类体现继承
  • 不同的异常类型体现多态

异常处理实战

try:
    # 可能出错的代码
    product.decrease_stock(100)
except InsufficientStockError as e:
    # 处理特定异常
    print(f"库存不足: {e}")
except ProductError as e:
    # 处理商品相关异常
    print(f"商品错误: {e}")
except Exception as e:
    # 处理其他异常
    print(f"未知错误: {e}")
finally:
    # 无论是否异常都会执行
    print("订单处理完成")

动手练习

  1. 运行上面的代码,理解商品类的设计
  2. 增加新功能:商品折扣功能,可以设置折扣率
  3. 添加商品评价功能,记录用户评价和评分

六、案例5:网络请求错误处理

问题场景

在开发Web应用或API接口时,经常需要处理网络请求错误:

  1. 连接超时
  2. 服务器错误(500)
  3. 认证失败(401)
  4. 资源不存在(404)
  5. 请求速率限制(429)

实战代码

# 网络请求错误处理系统
import requests
import time
from typing import Optional, Dict, Any, Union
from enum import Enum
from datetime import datetime, timedelta

# 自定义异常
class RequestError(Exception):
    """请求错误基类"""
    
    def __init__(self, message: str, url: Optional[str] = None, status_code: Optional[int] = None):
        self.message = message
        self.url = url
        self.status_code = status_code
        super().__init__(self.message)

class ConnectionTimeoutError(RequestError):
    """连接超时错误"""
    pass

class ServerError(RequestError):
    """服务器错误(5xx)"""
    pass

class ClientError(RequestError):
    """客户端错误(4xx)"""
    pass

class RateLimitError(RequestError):
    """请求频率限制错误"""
    
    def __init__(self, message: str, retry_after: int, url: Optional[str] = None):
        self.retry_after = retry_after
        super().__init__(message, url)

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

# 请求重试器
class RequestRetry:
    """请求重试管理器"""
    
    def __init__(
        self,
        max_retries: int = 3,
        backoff_factor: float = 0.5,
        timeout: float = 10.0
    ):
        self.max_retries = max_retries
        self.backoff_factor = backoff_factor
        self.timeout = timeout
        self.retry_count = 0
    
    def calculate_backoff(self) -> float:
        """计算退避时间"""
        return self.backoff_factor * (2 ** self.retry_count)
    
    def should_retry(self, error: Exception) -> bool:
        """判断是否应该重试"""
        if self.retry_count >= self.max_retries:
            return False
        
        # 根据错误类型决定是否重试
        retryable_errors = (
            ConnectionTimeoutError,
            ServerError,
            RateLimitError,
            requests.exceptions.Timeout,
            requests.exceptions.ConnectionError
        )
        
        return isinstance(error, retryable_errors)
    
    def wait_for_retry(self, error: Optional[RateLimitError] = None) -> None:
        """等待重试"""
        if isinstance(error, RateLimitError):
            wait_time = error.retry_after
            print(f"⏳ 速率限制,等待 {wait_time} 秒后重试...")
            time.sleep(wait_time)
        else:
            wait_time = self.calculate_backoff()
            print(f"⏳ 第{self.retry_count}次重试,等待 {wait_time:.1f} 秒...")
            time.sleep(wait_time)

# 请求处理器
class RequestHandler:
    """HTTP请求处理器"""
    
    def __init__(self):
        self.session = requests.Session()
        self.retry_manager = RequestRetry()
        
        # 配置默认请求头
        self.session.headers.update({
            'User-Agent': 'PythonRequestHandler/1.0',
            'Accept': 'application/json',
            'Accept-Encoding': 'gzip, deflate'
        })
    
    def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
        """处理响应"""
        if response.status_code >= 500:
            raise ServerError(
                f"服务器错误: {response.status_code}",
                url=response.url,
                status_code=response.status_code
            )
        
        elif response.status_code >= 400:
            if response.status_code == 401:
                raise AuthenticationError(
                    "认证失败,请检查API密钥",
                    url=response.url,
                    status_code=response.status_code
                )
            elif response.status_code == 404:
                raise ClientError(
                    "请求的资源不存在",
                    url=response.url,
                    status_code=response.status_code
                )
            elif response.status_code == 429:
                # 尝试从响应头获取重试时间
                retry_after = response.headers.get('Retry-After', 60)
                try:
                    retry_after = int(retry_after)
                except ValueError:
                    retry_after = 60
                
                raise RateLimitError(
                    "请求频率超过限制",
                    retry_after=retry_after,
                    url=response.url
                )
            else:
                raise ClientError(
                    f"客户端错误: {response.status_code}",
                    url=response.url,
                    status_code=response.status_code
                )
        
        # 尝试解析JSON响应
        try:
            return response.json()
        except ValueError:
            return {'raw_content': response.text}
    
    def make_request(
        self,
        method: str,
        url: str,
        **kwargs
    ) -> Dict[str, Any]:
        """
        发送HTTP请求
        
        参数:
            method: HTTP方法 (GET, POST, PUT, DELETE等)
            url: 请求URL
            **kwargs: 传递给requests的额外参数
        
        返回:
            解析后的响应数据
        
        异常:
            抛出各种自定义请求错误
        """
        # 重置重试计数
        self.retry_manager.retry_count = 0
        
        while True:
            try:
                print(f"\n🌐 发送 {method} 请求到: {url}")
                
                # 设置默认超时
                if 'timeout' not in kwargs:
                    kwargs['timeout'] = self.retry_manager.timeout
                
                # 发送请求
                response = self.session.request(method, url, **kwargs)
                
                # 处理响应
                result = self._handle_response(response)
                
                print(f"✅ 请求成功,状态码: {response.status_code}")
                return result
            
            except RateLimitError as e:
                # 速率限制错误,需要等待
                if self.retry_manager.should_retry(e):
                    self.retry_manager.retry_count += 1
                    self.retry_manager.wait_for_retry(e)
                    continue
                else:
                    raise
            
            except (ConnectionTimeoutError, ServerError,
                   requests.exceptions.Timeout,
                   requests.exceptions.ConnectionError) as e:
                
                # 可重试的错误
                if self.retry_manager.should_retry(e):
                    self.retry_manager.retry_count += 1
                    print(f"⚠️  请求失败: {e},准备重试...")
                    self.retry_manager.wait_for_retry()
                    continue
                else:
                    raise
            
            except RequestError as e:
                # 不可重试的请求错误
                print(f"❌ 请求错误: {e}")
                raise
            
            except Exception as e:
                # 其他未预期的错误
                print(f"💥 未预期的错误: {e}")
                raise RequestError(f"请求过程中发生未预期错误: {e}", url=url)

# API客户端示例
class GitHubAPIClient:
    """GitHub API客户端示例"""
    
    def __init__(self, token: Optional[str] = None):
        self.handler = RequestHandler()
        self.base_url = "https://api.github.com"
        
        if token:
            self.handler.session.headers.update({
                'Authorization': f'token {token}'
            })
    
    def get_user_info(self, username: str) -> Dict[str, Any]:
        """获取用户信息"""
        url = f"{self.base_url}/users/{username}"
        return self.handler.make_request('GET', url)
    
    def list_user_repos(self, username: str, page: int = 1, per_page: int = 10) -> Dict[str, Any]:
        """列出用户的仓库"""
        url = f"{self.base_url}/users/{username}/repos"
        params = {
            'page': page,
            'per_page': per_page,
            'sort': 'updated',
            'direction': 'desc'
        }
        return self.handler.make_request('GET', url, params=params)
    
    def search_repositories(self, query: str, language: Optional[str] = None) -> Dict[str, Any]:
        """搜索仓库"""
        url = f"{self.base_url}/search/repositories"
        
        # 构建搜索查询
        search_query = query
        if language:
            search_query += f" language:{language}"
        
        params = {
            'q': search_query,
            'sort': 'stars',
            'order': 'desc',
            'per_page': 5
        }
        
        return self.handler.make_request('GET', url, params=params)

# 演示函数
def demonstrate_error_handling():
    """演示错误处理的各种场景"""
    print("=" * 50)
    print("网络请求错误处理演示")
    print("=" * 50)
    
    # 创建请求处理器
    handler = RequestHandler()
    
    # 场景1:正常请求
    print("\n场景1: 正常请求")
    try:
        response = handler.make_request('GET', 'https://httpbin.org/json')
        print(f"响应数据: {response.get('slideshow', {})}")
    except RequestError as e:
        print(f"请求失败: {e}")
    
    # 场景2:服务器错误(5xx)
    print("\n场景2: 服务器错误(5xx)")
    try:
        response = handler.make_request('GET', 'https://httpbin.org/status/500')
    except ServerError as e:
        print(f"捕获到ServerError: {e.message} (状态码: {e.status_code})")
    
    # 场景3:认证错误(401)
    print("\n场景3: 认证错误(401)")
    try:
        response = handler.make_request('GET', 'https://httpbin.org/status/401')
    except AuthenticationError as e:
        print(f"捕获到AuthenticationError: {e.message}")
    except ClientError as e:
        print(f"捕获到ClientError: {e.message}")
    
    # 场景4:资源不存在(404)
    print("\n场景4: 资源不存在(404)")
    try:
        response = handler.make_request('GET', 'https://httpbin.org/status/404')
    except ClientError as e:
        print(f"捕获到ClientError: {e.message} (状态码: {e.status_code})")
    
    # 场景5:连接超时
    print("\n场景5: 连接超时(模拟)")
    try:
        # 使用一个非常短的超时时间
        response = handler.make_request('GET', 'https://httpbin.org/delay/5', timeout=0.1)
    except requests.exceptions.Timeout as e:
        print(f"捕获到Timeout错误: {e}")
        # 转换成自定义异常
        raise ConnectionTimeoutError("连接超时,请检查网络或稍后重试")
    
    # 场景6:GitHub API实际使用
    print("\n场景6: GitHub API实际使用")
    github = GitHubAPIClient()
    
    try:
        # 获取Python官方账号信息
        user_info = github.get_user_info("python")
        print(f"Python官方账号:")
        print(f"  名称: {user_info.get('name', 'N/A')}")
        print(f"  关注者: {user_info.get('followers', 0)}")
        print(f"  仓库数: {user_info.get('public_repos', 0)}")
        
        # 搜索Python相关的仓库
        search_result = github.search_repositories("python", "python")
        if 'items' in search_result:
            print(f"\n最受欢迎的Python仓库:")
            for i, repo in enumerate(search_result['items'][:3], 1):
                print(f"  {i}. {repo['name']} - ⭐ {repo['stargazers_count']}")
        
    except RequestError as e:
        print(f"GitHub API请求失败: {e}")
    
    # 场景7:重试机制演示
    print("\n场景7: 重试机制演示")
    
    class MockRequestHandler:
        """模拟请求处理器,用于演示重试"""
        
        def __init__(self, fail_times: int = 2):
            self.fail_times = fail_times
            self.attempts = 0
        
        def make_request(self, method: str, url: str, **kwargs) -> Dict[str, Any]:
            self.attempts += 1
            
            if self.attempts <= self.fail_times:
                print(f"  第{self.attempts}次尝试: 模拟服务器错误...")
                raise ServerError(f"模拟服务器错误(尝试 {self.attempts}/{self.fail_times})")
            else:
                print(f"  第{self.attempts}次尝试: 成功!")
                return {'status': 'success', 'attempts': self.attempts}
    
    # 创建模拟处理器
    mock_handler = MockRequestHandler(fail_times=2)
    retry_manager = RequestRetry(max_retries=3)
    
    for attempt in range(1, retry_manager.max_retries + 2):
        try:
            result = mock_handler.make_request('GET', 'https://example.com')
            print(f"最终结果: {result}")
            break
        except ServerError as e:
            print(f"第{attempt}次请求失败: {e}")
            if attempt <= retry_manager.max_retries:
                retry_manager.retry_count = attempt
                retry_manager.wait_for_retry()
            else:
                print("已达到最大重试次数,放弃请求")
    
    print("\n" + "=" * 50)
    print("错误处理总结:")
    print("=" * 50)
    print("""
    1. 连接超时错误: 网络问题或服务器响应慢
        - 应对策略: 重试 + 退避算法
    
    2. 服务器错误 (5xx): 服务器端问题
        - 应对策略: 重试 + 联系服务提供商
    
    3. 客户端错误 (4xx):
        - 401: 认证失败,检查API密钥
        - 404: 资源不存在,检查URL
        - 429: 请求频率限制,等待后重试
    
    4. 业务逻辑错误: 虽然HTTP请求成功,但业务逻辑失败
        - 应对策略: 解析响应内容,抛出业务异常
    
    5. 未预期错误: 程序bug或环境问题
        - 应对策略: 记录日志 + 优雅降级
    """)

# 高级错误处理:断路器模式
class CircuitBreaker:
    """断路器模式实现"""
    
    def __init__(self, failure_threshold: int = 5, recovery_timeout: int = 60):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.failure_count = 0
        self.last_failure_time: Optional[datetime] = None
        self.state = "CLOSED"  # 状态: CLOSED, OPEN, HALF_OPEN
    
    def record_failure(self):
        """记录失败"""
        self.failure_count += 1
        self.last_failure_time = datetime.now()
        
        if self.failure_count >= self.failure_threshold:
            self.state = "OPEN"
            print(f"🔴 断路器打开,停止发送请求")
    
    def record_success(self):
        """记录成功"""
        self.failure_count = 0
        self.state = "CLOSED"
        print(f"🟢 断路器关闭,恢复正常请求")
    
    def allow_request(self) -> bool:
        """判断是否允许请求"""
        if self.state == "CLOSED":
            return True
        
        elif self.state == "OPEN":
            # 检查是否过了恢复时间
            if self.last_failure_time:
                elapsed = (datetime.now() - self.last_failure_time).total_seconds()
                if elapsed > self.recovery_timeout:
                    self.state = "HALF_OPEN"
                    print(f"🟡 断路器半开,尝试恢复")
                    return True
            return False
        
        elif self.state == "HALF_OPEN":
            return True
        
        return False

# 使用示例
if __name__ == "__main__":
    demonstrate_error_handling()
    
    print("\n" + "=" * 50)
    print("完整请求处理流程示例:")
    print("=" * 50)
    
    # 创建综合示例
    handler = RequestHandler()
    
    # 演示try-except-finally的完整使用
    print("\n演示完整的try-except-finally块:")
    
    response_data = None
    start_time = time.time()
    
    try:
        print("1. 发送请求...")
        response_data = handler.make_request(
            'GET',
            'https://httpbin.org/status/200'
        )
        print(f"2. 请求成功,数据: {response_data}")
        
    except ConnectionTimeoutError as e:
        print(f"3. 连接超时错误: {e}")
        # 可以在这里记录日志或发送告警
        
    except ServerError as e:
        print(f"3. 服务器错误: {e}")
        # 可以在这里实现降级逻辑
        
    except ClientError as e:
        print(f"3. 客户端错误: {e}")
        # 根据错误类型处理
        
    except RateLimitError as e:
        print(f"3. 速率限制错误,需要等待 {e.retry_after} 秒")
        
    except Exception as e:
        print(f"3. 未预期错误: {e}")
        # 最后捕获所有未预期的错误
        
    finally:
        elapsed = time.time() - start_time
        print(f"4. finally块执行: 请求耗时 {elapsed:.2f} 秒")
        print("5. 清理资源...")
        # 这里可以关闭连接、清理临时文件等
    
    print("\n✅ 错误处理演示完成!")
    print("""
    关键要点:
    1. 根据错误类型选择合适的处理策略
    2. 可重试错误使用退避算法
    3. 不可重试错误给用户友好提示
    4. 始终在finally块中清理资源
    5. 记录详细的错误日志便于排查
    """)

知识点解析:异常处理完整实践

1. try-except-finally结构

try:
    # 可能出错的代码
    response = requests.get(url, timeout=10)
except ConnectionTimeoutError:
    # 处理特定异常
    print("连接超时")
except Exception as e:
    # 处理其他异常
    print(f"未知错误: {e}")
finally:
    # 无论是否异常都会执行
    print("清理资源")

2. 自定义异常类

class RequestError(Exception):
    def __init__(self, message: str, url: Optional[str] = None):
        self.message = message
        self.url = url
        super().__init__(self.message)

class ServerError(RequestError):
    pass  # 继承父类,可以添加额外属性

3. 异常链

try:
    # 业务代码
except OriginalError as e:
    raise CustomError("自定义错误消息") from e
    # 保留原始异常信息

动手练习

  1. 运行错误处理演示代码,观察不同错误场景的处理
  2. 增加新功能:请求日志记录,保存到文件
  3. 实现邮件告警功能,当服务器频繁错误时发送告警

七、知识总结

知识体系总结

通过今天的5个实战案例,我们构建了一个完整的Python基础知识体系:

知识点

案例应用

核心技能

变量与数据类型

用户注册验证

字符串操作、类型注解、数据验证

流程控制

文件批量处理

if-elif-else、条件判断、文件过滤

循环结构

员工信息管理

for循环、列表推导、字典遍历

函数定义

电商商品类

四种参数传递、装饰器、类方法

异常处理

网络请求处理

try-except-finally、自定义异常

常见问题解答

Q1: 学完这些案例,我能做什么实际项目?

A: 学完今天的内容,你已经可以:

  1. 开发简单的Web表单验证系统
  2. 编写办公自动化脚本处理文件
  3. 设计基本的电商系统商品模块
  4. 开发员工信息管理系统
  5. 处理网络API请求的错误情况

Q2: 如何记住这么多语法?

A: 不要死记硬背!记住:

  1. 变量命名要有意义(如user_name而不是x
  2. 函数参数顺序:位置→*args→默认→**kwargs
  3. 异常处理结构:特定异常在前,通用异常在后
  4. 循环优化:能用列表推导就不用for循环

Q3: 下一步该学什么?

A: 建议的学习路径:

  1. 巩固基础:把今天的案例自己重写一遍
  2. 学习数据结构:列表、字典、集合的高级用法
  3. 面向对象进阶:继承、多态、设计模式
  4. Web框架:Flask或Django
  5. 数据库操作:SQLAlchemy或Django ORM

八、学习效果自测

在你开始动手实践之前,先做一个简单的自测,检查今天的学习效果:

自测题(共10题,每题10分)

1. 变量与数据类型

  • 如何定义一个包含类型注解的用户名字符串变量?
  • Python中哪些数据类型是可变的?哪些是不可变的?

2. 条件判断

  • 编写一个函数,根据用户年龄返回不同的票价(儿童半价、成人全价、老人免费)
  • if-elif-else结构中,多个条件都满足时会发生什么?

3. 循环结构

  • for循环和while循环的主要区别是什么?
  • 什么情况下应该使用列表推导式而不是普通for循环?

4. 函数参数

  • 解释Python函数参数传递的四种方式

  • 下面的函数调用中,哪些参数是位置参数,哪些是关键参数?

    python

    def process_data(name, age=25, *args, **kwargs):
        pass
    
    process_data("张三", 30, "额外", city="北京")
    

5. 异常处理

  • try-except-finally结构中,finally块什么时候执行?
  • 如何自定义一个"文件找不到"的异常类?

6. 字符串操作

  • 如何检查一个字符串是否包含空格?
  • 如何将一个邮箱地址按@符号分割成两部分?

7. 列表操作

  • 如何向列表中添加多个元素?
  • 如何获取列表中的最后三个元素?

8. 面向对象基础

  • 类中的__init__方法有什么作用?
  • 如何定义一个只能通过getter访问的私有属性?

9. 错误处理策略

  • 遇到网络连接超时错误,应该采用什么重试策略?
  • 如何避免在异常处理中捕获过于宽泛的Exception?

10. 实战思维

  • 如果让你设计一个简单的图书管理系统,你会首先定义哪些类?
  • 在处理用户上传文件时,需要考虑哪些安全问题?

评分标准

  • 90-100分:优秀!你已经掌握了Python基础的核心概念
  • 70-89分:良好!理解了大部分概念,需要加强实践
  • 50-69分:及格!需要复习重点概念,多做练习
  • 50分以下:需要重新学习本篇内容,从案例1开始动手实践

重要提示 :无论得分多少,真正的学习效果要通过动手写代码来验证。现在开始完成下面的挑战任务吧!

技术支持

如果在学习过程中遇到问题:

  1. 专栏评论区 :每篇教程下方都可以提问

  2. 代码仓库 :所有案例代码都已上传到GitHub

  3. 学习社群 :购买专栏后可加入专属学习社群


附录:Python基础速查表

1. 常用数据类型操作

字符串

# 检查包含
if ' ' in username:  # 检查空格
if '@' in email:    # 检查@符号

# 分割与连接
parts = email.split('@')
full_name = ' '.join(['张', '三'])

# 格式化
name = f"用户: {username}"

列表

# 创建
numbers = [1, 2, 3]
mixed = ['a', 1, True]

# 操作
numbers.append(4)      # 添加元素
numbers.insert(0, 0)   # 插入元素
last = numbers.pop()   # 移除最后一个

# 切片
first_three = numbers[:3]
last_two = numbers[-2:]

字典

# 创建
user = {'name': '张三', 'age': 25}

# 访问
name = user['name']
age = user.get('age', 0)  # 安全访问

# 遍历
for key, value in user.items():
    print(f"{key}: {value}")

2. 流程控制要点

if-elif-else结构

# 完整的条件判断
if score >= 90:
    grade = 'A'
elif score >= 80:
    grade = 'B'
elif score >= 60:
    grade = 'C'
else:
    grade = 'F'

注意事项 :

  • 条件从上到下检查,第一个满足的条件执行
  • 可以有多个elif,但只能有一个else
  • 条件表达式可以是任何返回布尔值的表达式

3. 循环优化技巧

传统for循环

result = []
for i in range(10):
    result.append(i * 2)

列表推导式 (更简洁)

result = [i * 2 for i in range(10)]

带条件的列表推导式

even_numbers = [i for i in range(20) if i % 2 == 0]

4. 函数参数记忆口诀

位变关 (位置、可变、关键字)

  1. 位置参数必填
  2. *args接收多个位置参数
  3. 默认参数有默认值
  4. **kwargs接收多个关键字参数

5. 异常处理最佳实践

try:
    # 可能出错的代码
    result = risky_operation()
except SpecificError as e:
    # 处理特定错误
    logger.error(f"特定错误: {e}")
    fallback_operation()
except AnotherError as e:
    # 处理另一种错误
    logger.warning(f"警告: {e}")
except Exception as e:
    # 最后捕获通用异常
    logger.critical(f"未预期错误: {e}")
    raise  # 重新抛出或处理
finally:
    # 总是执行清理
    cleanup_resources()

6. 调试技巧

使用print调试

print(f"[DEBUG] 变量值: {variable}")
print(f"[DEBUG] 类型: {type(variable)}")

使用断言

assert len(items) > 0, "列表不能为空"
assert price > 0, "价格必须为正数"

记录日志

import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.info("程序启动")
logger.debug(f"详细数据: {data}")