Module 5: 函数与模块
让代码可复用 —— 从重复劳动到优雅编程
本章概览
写代码时,你会发现很多操作需要重复执行:计算统计量、清洗数据、运行回归。函数让你把这些操作封装起来,随时调用,避免重复代码。模块则让你组织和重用别人(或自己)写好的函数。掌握函数和模块,你的代码将从"脚本"升级为"程序"。
学习目标
学完本章后,你将能够:
- 理解函数的概念和作用
- 定义和调用自己的函数
- 掌握参数传递(位置参数、关键字参数、默认参数)
- 使用
*args和**kwargs处理可变参数 - 理解 Lambda 函数和应用场景
- 导入和使用标准库与第三方模块
- 组织自己的代码为模块和包
- 对比 Stata 的
program和 R 的function()
章节内容
01 - 函数基础
核心问题: 如何让代码可复用?
核心内容:
- 函数的定义与调用
- 参数(parameters)和返回值(return)
- 文档字符串(docstring)
- 局部变量 vs 全局变量
None的含义- 对比 Stata(
program define)和 R(function())
实际应用:
def calculate_bmi(weight, height):
"""计算 BMI 指数
参数:
weight: 体重(千克)
height: 身高(米)
返回:
BMI 值
"""
bmi = weight / (height ** 2)
return bmi
# 调用函数
result = calculate_bmi(70, 1.75)
print(f"BMI: {result:.2f}") # BMI: 22.86研究场景:
- 计算统计量(均值、标准差、分位数)
- 数据清洗(缺失值处理、异常值检测)
- 重复分析(多个模型、多个子样本)
- 稳健性检验
02 - 函数参数
核心问题: 如何灵活地向函数传递数据?
核心内容:
- 位置参数(positional arguments):按顺序传递
- 关键字参数(keyword arguments):按名称传递
- 默认参数(default arguments):提供默认值
- 可变位置参数(
*args):接受任意数量的位置参数 - 可变关键字参数(
**kwargs):接受任意数量的关键字参数 - 参数顺序规则
- 参数解包(
*,**)
实际应用:
def run_regression(y, X, model_type="OLS", robust=True, **options):
"""运行回归分析
参数:
y: 因变量
X: 自变量
model_type: 模型类型(默认 OLS)
robust: 是否使用稳健标准误(默认 True)
**options: 其他选项
"""
print(f"模型: {model_type}")
print(f"稳健标准误: {robust}")
print(f"其他选项: {options}")
# 灵活调用
run_regression(y, X) # 使用默认参数
run_regression(y, X, model_type="Logit") # 修改模型类型
run_regression(y, X, robust=False, cluster="firm_id") # 传递额外选项研究场景:
- 提供默认配置(显著性水平、迭代次数)
- 灵活的模型选项
- 批量处理(传递多个参数组合)
03 - Lambda 函数
核心问题: 什么时候用匿名函数?
核心内容:
- Lambda 语法:
lambda arguments: expression - 与普通函数的对比
- 使用场景:
- 排序(
sorted(),list.sort()) - 映射(
map()) - 过滤(
filter()) - Pandas 数据操作
- 排序(
- Lambda 的局限性
实际应用:
# 按收入排序学生列表
students = [
{"name": "Alice", "income": 50000},
{"name": "Bob", "income": 75000},
{"name": "Carol", "income": 60000}
]
sorted_students = sorted(students, key=lambda x: x["income"])
# Pandas 数据清洗
df["log_income"] = df["income"].apply(lambda x: np.log(x) if x > 0 else None)
# 过滤高收入者
high_earners = list(filter(lambda x: x["income"] > 60000, students))何时使用 Lambda?
- 简单的一次性函数
- 排序、映射、过滤
- Pandas
apply()操作 - 复杂逻辑(用普通函数)
- 需要文档字符串(用普通函数)
04 - 模块与包
核心问题: 如何组织和重用代码?
核心内容:
- 模块(Module):单个
.py文件 - 包(Package):包含多个模块的文件夹
- 导入方式:
import modulefrom module import functionimport module as aliasfrom module import *(不推荐)
- 标准库介绍:
math:数学函数statistics:统计函数random:随机数生成datetime:日期时间处理os、sys:系统操作
- 第三方库安装(
pip install) - 创建自己的模块和包
实际应用:
# 导入标准库
import math
import statistics as stats
from datetime import datetime
# 使用数学函数
log_income = math.log(50000)
sqrt_value = math.sqrt(100)
# 计算统计量
mean_income = stats.mean([50000, 60000, 75000])
median_income = stats.median([50000, 60000, 75000])
# 导入数据分析库
import numpy as np
import pandas as pd
import statsmodels.api as sm
# 创建自己的模块
# my_stats.py
def winsorize(data, lower=0.01, upper=0.99):
"""缩尾处理"""
p_lower = np.quantile(data, lower)
p_upper = np.quantile(data, upper)
return np.clip(data, p_lower, p_upper)
# 在其他文件中导入
from my_stats import winsorize
clean_data = winsorize(incomes)研究场景:
- 使用 NumPy 进行数值计算
- 使用 Pandas 处理数据
- 使用 statsmodels 运行回归
- 组织自己的数据清洗函数库
05 - 小结和复习
内容:
- 函数设计最佳实践
- 常见错误和调试技巧
- 模块组织建议
- 综合练习题
- Stata/R/Python 对比
Stata vs R vs Python 函数对比
定义函数
| 语言 | 语法 |
|---|---|
| Stata | program define func_name |
| R | func_name <- function(args) { ... } |
| Python | def func_name(args): ... |
示例:计算 BMI
Stata:
program define calc_bmi
args weight height
gen bmi = `weight' / (`height'^2)
end
calc_bmi 70 1.75R:
calc_bmi <- function(weight, height) {
bmi <- weight / (height^2)
return(bmi)
}
result <- calc_bmi(70, 1.75)Python:
def calc_bmi(weight, height):
bmi = weight / (height ** 2)
return bmi
result = calc_bmi(70, 1.75)如何学习本章?
学习路线
第 1 天(3小时): 函数基础
- 阅读 01 - 函数基础
- 定义简单函数
- 理解返回值和文档字符串
第 2 天(3小时): 函数参数
- 阅读 02 - 函数参数
- 练习位置/关键字/默认参数
- 理解
*args和**kwargs
第 3 天(2小时): Lambda 函数
- 阅读 03 - Lambda 函数
- 用 Lambda 排序和过滤
- 在 Pandas 中使用 Lambda
第 4 天(3小时): 模块与包
- 阅读 04 - 模块与包
- 导入和使用标准库
- 创建自己的模块
第 5 天(2小时): 复习和综合应用
- 完成 05 - 小结和复习
- 编写完整的分析函数库
- 组织代码为模块
总时间: 13 小时(1-2 周)
最小化学习路径
如果时间有限:
必学(核心概念,8小时):
- 01 - 函数基础(完整学习)
- 02 - 函数参数(位置/关键字/默认)
- 04 - 模块与包(导入标准库)
选学(进阶技巧):
*args和**kwargs- Lambda 函数
- 创建自己的包
学习建议
从需求出发
- 发现重复代码 → 提取为函数
- 需要配置选项 → 使用默认参数
- 代码越来越长 → 拆分为模块
函数设计原则
- 单一职责:一个函数只做一件事
- 有意义的名称:
calculate_bmi()比func1()好 - 写文档字符串:说明功能、参数、返回值
- 避免副作用:尽量不修改全局变量
实践项目 创建一个
my_stats.py模块,包含:python# my_stats.py def mean(data): """计算均值""" return sum(data) / len(data) def std(data): """计算标准差""" m = mean(data) variance = sum((x - m) ** 2 for x in data) / len(data) return variance ** 0.5 def winsorize(data, lower=0.01, upper=0.99): """缩尾处理""" import numpy as np p_lower = np.quantile(data, lower) p_upper = np.quantile(data, upper) return np.clip(data, p_lower, p_upper)对比学习
- 用 Python 复现 Stata 的
program - 用 Python 复现 R 的
function() - 理解语法差异背后的哲学
- 用 Python 复现 Stata 的
常见问题
Q: 函数和变量有什么区别? A: 变量存储数据,函数存储操作。变量是"名词",函数是"动词"。
Q: 什么时候应该写函数? A: 遵循 DRY 原则(Don't Repeat Yourself)。如果一段代码用了 3 次以上,就应该提取为函数。
Q: 为什么需要文档字符串? A: 几周后你会忘记函数的用法。文档字符串是写给未来的自己(和合作者)的说明书。
Q: return 和 print() 有什么区别? A:
return:将值返回给调用者,可以赋值给变量print():仅在屏幕上显示,不返回值
def bad_function(x):
print(x * 2) # 只打印,不返回
def good_function(x):
return x * 2 # 返回值
result = bad_function(5) # result = None
result = good_function(5) # result = 10Q: 什么时候用 Lambda,什么时候用普通函数? A:
- Lambda:简单的一行表达式(排序、映射、过滤)
- 普通函数:复杂逻辑、多行代码、需要文档字符串
Q: 如何找到有用的第三方库? A:
- 数据分析:
numpy,pandas,statsmodels - 数据可视化:
matplotlib,seaborn,plotly - 机器学习:
scikit-learn,xgboost,lightgbm - 文本分析:
nltk,spaCy,transformers - 网络爬虫:
requests,beautifulsoup4,scrapy
下一步
完成本章后,你将掌握:
- 编写可复用的函数
- 灵活使用参数传递
- 导入和组织模块
- 代码从"脚本"升级为"程序"
在 Module 6-7 中,我们将学习 Pandas,这是 Python 数据分析的核心库,整合了我们学过的所有概念!
在 Module 8-9 中,我们将学习数据可视化和高级数据处理技巧。
恭喜!完成前 5 个模块,你已经掌握了 Python 的核心语法!接下来就是数据分析的实战了!