CHAPTER 05

函数与模块化编程

把可复用的逻辑封装成函数,AI 项目中每个数据预处理、评估步骤都应该是函数。模块化让代码清晰、可测试、可复用。

def —— 定义函数

函数是封装好的、可复用的代码块。给它一个名字,需要时调用它,不用重复写相同的逻辑。在 AI 开发中,几乎所有操作都应该封装成函数:数据预处理、模型评估、结果可视化……

核心概念

函数的三要素:参数、函数体、返回值

参数是函数的"输入",函数体是处理逻辑,return 是"输出"。没有 return 的函数默认返回 None。把这三部分想清楚,函数就设计好了。

PYTHON · 函数基础
# 定义一个函数
def greet(name):
    """打招呼函数(这是文档字符串,用三引号)"""
    return f"你好,{name}!"

# 调用函数
print(greet("Claude"))   # 你好,Claude!
print(greet("GPT"))      # 你好,GPT!

# AI 场景:计算模型准确率
def calc_accuracy(y_true, y_pred):
    """计算分类准确率"""
    correct = sum(1 for t, p in zip(y_true, y_pred) if t == p)
    return correct / len(y_true)

labels      = [1, 0, 1, 1, 0]
predictions = [1, 0, 0, 1, 0]
print(calc_accuracy(labels, predictions))  # 0.8

默认参数与关键字参数

PYTHON · 参数进阶
# 默认参数:调用时可以省略
def train_model(data, lr=0.001, epochs=10, batch_size=32):
    print(f"lr={lr}, epochs={epochs}, batch={batch_size}")
    # ... 训练逻辑

train_model(data)                         # 用全部默认值
train_model(data, lr=0.01)               # 只改 lr
train_model(data, epochs=50, lr=0.005)   # 关键字参数,顺序可以变

# *args:接收任意数量的位置参数
def average(*numbers):
    return sum(numbers) / len(numbers)

print(average(1, 2, 3))        # 2.0
print(average(10, 20, 30, 40)) # 25.0

# **kwargs:接收任意数量的关键字参数(常用于配置传递)
def build_model(**config):
    for key, val in config.items():
        print(f"  {key}: {val}")

build_model(layers=4, hidden=256, dropout=0.1)
🤖

AI 框架中的参数模式

PyTorch 的 torch.optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999)) 就是大量使用默认参数和关键字参数的典型例子。你只需要传关心的参数,其余都有合理默认值。

返回值与多返回值

Python 函数可以返回多个值(实际上是返回一个元组)。AI 训练函数通常同时返回训练损失和准确率,这时多返回值非常方便。

PYTHON · 多返回值
# 返回多个值(实际返回的是元组)
def train_epoch(model, data):
    # 模拟训练过程
    loss = 0.35
    accuracy = 0.87
    return loss, accuracy   # 返回两个值

# 解包接收多个返回值
train_loss, train_acc = train_epoch(model, data)
print(f"Loss: {train_loss}, Acc: {train_acc}")

# 也可以返回字典(更清晰,适合参数多的情况)
def evaluate_model(y_true, y_pred):
    from collections import Counter
    tp = sum(1 for t, p in zip(y_true, y_pred) if t == p == 1)
    accuracy = sum(1 for t, p in zip(y_true, y_pred) if t == p) / len(y_true)
    return {
        "accuracy": accuracy,
        "true_positives": tp,
        "total": len(y_true)
    }

metrics = evaluate_model([1,0,1,1], [1,0,0,1])
print(metrics["accuracy"])  # 0.75

lambda —— 匿名函数

lambda 是一种简洁的单行函数写法,适合写简单的、一次性的小函数。在 AI 数据处理中,经常配合 map()sorted()filter() 使用。

PYTHON · lambda 用法
# lambda 基本语法:lambda 参数: 表达式
square = lambda x: x ** 2
print(square(5))  # 25

# 等价于:
def square(x): return x ** 2

# 常见用法:作为排序的 key
models = [
    {"name": "ResNet", "accuracy": 0.92},
    {"name": "VGG",    "accuracy": 0.88},
    {"name": "EfficientNet", "accuracy": 0.95},
]
# 按准确率排序
sorted_models = sorted(models, key=lambda m: m["accuracy"], reverse=True)
for m in sorted_models:
    print(f"{m['name']}: {m['accuracy']}")

# map() 对列表每个元素做变换
raw_scores = [0.3, 1.5, -0.2, 2.1]
normalized = list(map(lambda x: max(0, min(1, x)), raw_scores))
print(normalized)  # [0.3, 1, 0, 1]

# filter() 过滤列表
all_losses = [0.5, 2.1, 0.3, 3.0, 0.1]
good_runs = list(filter(lambda x: x < 0.5, all_losses))
print(good_runs)  # [0.3, 0.1]

作用域:局部变量与全局变量

函数内部定义的变量是局部变量,只在函数内有效。函数外定义的是全局变量。理解作用域,能避免 AI 项目中很多难以追踪的 bug。

PYTHON · 作用域
LEARNING_RATE = 0.001  # 全局变量(约定用大写)

def update_weights(gradient):
    # 读取全局变量没问题
    delta = LEARNING_RATE * gradient  # 局部变量
    return delta

# 如果函数内要修改全局变量,需要 global 声明
epoch_count = 0

def run_epoch():
    global epoch_count     # 声明要修改全局变量
    epoch_count += 1
    print(f"当前是第 {epoch_count} 轮")

run_epoch()  # 当前是第 1 轮
run_epoch()  # 当前是第 2 轮
⚠️

尽量避免修改全局变量

过度使用 global 会让代码难以理解和测试。AI 项目中更好的做法是用类(Class)或者配置字典来管理状态,或者通过参数传递和返回值来传递数据。

模块与 import —— 组织代码

每个 .py 文件就是一个模块。通过 import 可以把其他文件的函数引入进来使用。Python 强大的生态就建立在这个机制上——NumPy、PyTorch 都是一个个模块/包。

PYTHON · import 各种用法
# 1. 导入整个模块
import numpy
arr = numpy.array([1, 2, 3])

# 2. 用别名(最常见写法)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
arr = np.array([1, 2, 3])  # 用 np 代替 numpy

# 3. 只导入需要的部分
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, f1_score
from openai import OpenAI

# 4. Python 标准库也是模块
import os
import json
import time
import random
from pathlib import Path
from datetime import datetime

# 读取 JSON 配置(AI 项目常用)
with open("config.json") as f:
    config = json.load(f)

# 用 Path 处理文件路径(跨平台)
data_dir = Path("data") / "train"
model_path = Path("models") / "best_model.pt"

创建自己的模块

PYTHON · utils.py(自定义模块)
# utils.py —— 工具函数模块

def normalize(data, min_val=None, max_val=None):
    """Min-Max 归一化,将数据缩放到 [0, 1]"""
    if min_val is None:
        min_val = min(data)
    if max_val is None:
        max_val = max(data)
    return [(x - min_val) / (max_val - min_val) for x in data]

def train_val_split(data, ratio=0.8):
    """切分训练集和验证集"""
    n = int(len(data) * ratio)
    return data[:n], data[n:]

# main.py —— 主程序
from utils import normalize, train_val_split

raw = [10, 20, 30, 40, 50]
normalized = normalize(raw)
print(normalized)  # [0.0, 0.25, 0.5, 0.75, 1.0]
💡

AI 项目的标准文件结构

典型 AI 项目会有 utils.py(工具函数)、model.py(模型定义)、train.py(训练脚本)、evaluate.py(评估脚本)。把不同职责的代码分文件存放,这就是模块化的精髓。

综合示例:数据预处理流水线

把本章学的函数、默认参数、多返回值、模块都用上,构建一个 AI 项目中典型的数据预处理流水线:

PYTHON · 数据预处理流水线
import json
import random

def load_data(filepath):
    """从 JSON 文件加载数据"""
    with open(filepath) as f:
        return json.load(f)

def clean_data(samples):
    """过滤掉空文本和过短的样本"""
    return [s for s in samples if s.get("text") and len(s["text"]) > 10]

def split_dataset(data, train_ratio=0.8, seed=42):
    """随机切分训练集和测试集"""
    random.seed(seed)
    shuffled = data.copy()
    random.shuffle(shuffled)
    n = int(len(shuffled) * train_ratio)
    return shuffled[:n], shuffled[n:]  # 返回两个值

def preprocess_pipeline(filepath, **kwargs):
    """完整预处理流水线"""
    raw   = load_data(filepath)
    clean = clean_data(raw)
    train, test = split_dataset(clean, **kwargs)

    print(f"原始: {len(raw)} 条 → 清洗后: {len(clean)} 条")
    print(f"训练集: {len(train)} | 测试集: {len(test)}")
    return train, test

# 一行调用整个流水线
train_set, test_set = preprocess_pipeline("dataset.json", train_ratio=0.85)

函数化的好处

当你想换一个数据集,只需要改 filepath;当你想调整切分比例,只需改 train_ratio。每个函数只做一件事,测试和调试也更容易。这是 AI 项目工程化的核心思维。