函数与模块化编程
把可复用的逻辑封装成函数,AI 项目中每个数据预处理、评估步骤都应该是函数。模块化让代码清晰、可测试、可复用。
函数核心概念
- 函数(Function) 具名的可复用代码块,接受输入(参数),执行逻辑,产生输出(返回值)。函数是代码复用的基本单位,DRY 原则(Don't Repeat Yourself)的主要实现手段。
-
参数(Parameter)vs 实参(Argument)
参数是函数定义中的变量名(
def f(x)中的x);实参是调用时传入的具体值(f(42)中的42)。很多人混用这两个词,但严格区分有助于理解函数调用机制。 -
默认参数(Default Parameter)
函数定义时为参数指定默认值,调用时可以省略该参数。AI 框架中大量使用:
Adam(lr=0.001, betas=(0.9, 0.999))。有默认值的参数必须放在没有默认值的参数之后。 - 闭包(Closure) 内层函数记住了外层函数的局部变量,即使外层函数已经执行完毕。用于创建带有"记忆"的函数,如带有超参数的损失函数工厂。
-
装饰器(Decorator)
用
@decorator语法在不修改函数代码的情况下增加功能。PyTorch 中@torch.no_grad()、@staticmethod、@property都是装饰器的应用。
def —— 定义函数
函数是封装好的、可复用的代码块。给它一个名字,需要时调用它,不用重复写相同的逻辑。在 AI 开发中,几乎所有操作都应该封装成函数:数据预处理、模型评估、结果可视化……
函数的三要素:参数、函数体、返回值
参数是函数的"输入",函数体是处理逻辑,return 是"输出"。没有 return 的函数默认返回 None。把这三部分想清楚,函数就设计好了。
# 定义一个函数
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
默认参数与关键字参数
# 默认参数:调用时可以省略
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 训练函数通常同时返回训练损失和准确率,这时多返回值非常方便。
# 返回多个值(实际返回的是元组)
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() 使用。
# 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。
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 都是一个个模块/包。
# 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"
创建自己的模块
# 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 项目中典型的数据预处理流水线:
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 项目工程化的核心思维。
装饰器与高阶函数
装饰器是 Python 最优雅的特性之一,本质是"接收函数并返回新函数的函数"。AI 框架大量使用装饰器来增强功能。
import time
import functools
# 计时装饰器:测量函数执行时间
def timer(func):
"""记录函数的执行时间"""
@functools.wraps(func) # 保留原函数的名字和文档
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) # 调用原函数
elapsed = time.time() - start
print(f"{func.__name__} 耗时 {elapsed:.3f}s")
return result
return wrapper
# 使用装饰器语法
@timer
def train_epoch(dataloader):
"""模拟一轮训练"""
time.sleep(0.1) # 模拟训练耗时
return 0.42
loss = train_epoch(None) # train_epoch 耗时 0.100s
# 带参数的装饰器(工厂模式)
def retry(max_attempts=3):
"""失败自动重试 max_attempts 次"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
print(f"第 {attempt+1} 次失败,重试...")
return wrapper
return decorator
@retry(max_attempts=3)
def call_api(prompt):
"""调用 AI API,网络不稳定时自动重试"""
# ... API 调用逻辑
pass
默认参数的可变对象陷阱
永远不要用可变对象(list、dict)作为默认参数!def f(data=[]) 中的 [] 只在函数定义时创建一次,所有调用共享同一个列表,导致意外的状态积累。正确做法:def f(data=None): if data is None: data = []。
本章小结
函数是 Python 代码组织的核心单位。关键要点:参数分为位置参数、默认参数、*args(可变位置参数)和 **kwargs(可变关键字参数);函数可以返回多个值(实为 tuple 解包);lambda 适合简单的一次性匿名函数;装饰器是增强函数功能的优雅方式;每个 .py 文件是一个模块,import 让代码可复用。AI 项目标准结构:model.py、train.py、evaluate.py、utils.py。注意:可变对象(list/dict)不能用作默认参数值。