函数与模块:学会"一次写好,到处用"
函数与模块:学会"一次写好,到处用"
一、问题背景:为什么同样的事要做50遍?
刚做FAB数据分析时,我有一段痛苦的经历。
每个Lot的膜厚数据都需要算均值和标准差。当时我的代码是这样的:
```python
Lot A
data_a = [1250.5, 1248.3, 1251.2]
avg_a = sum(data_a) / len(data_a) # 算均值
diff_a = [(x - avg_a)**2 for x in data_a]
std_a = (sum(diff_a) / len(diff_a))**0.5 # 算标准差
print(f"Lot A: 平均={avg_a:.1f}, 标准差={std_a:.2f}")
Lot B
data_b = [1245.2, 1252.1, 1247.8]
avg_b = sum(data_b) / len(data_b)
diff_b = [(x - avg_b)**2 for x in data_b]
std_b = (sum(diff_b) / len(diff_b))**0.5
print(f"Lot B: 平均={avg_b:.1f}, 标准差={std_b:.2f}")
Lot C...D...E... 同样的代码重复几十遍
```
后面来了50个Lot。我复制粘贴了50遍。
每天8小时,4小时在按Ctrl+C、Ctrl+V。
问题是:如果均值公式要改(比如改成加权平均),要改50个地方。漏一个就bug。
学完这一篇,你能做到:
1. 用函数把重复代码打包成"一个公式"
2. 把函数存到单独的.py文件里,以后任何项目直接调用
3. 代码行数减少80%,bug数减少99%
---
二、技术原理:函数就是"计算公式"
2.1 一个最简单的函数
```python
定义函数:把"算均值"这个操作打包
def calc_avg(data):
"""计算平均值"""
return sum(data) / len(data)
使用函数——一行搞定
avg_a = calc_avg([1250.5, 1248.3, 1251.2])
avg_b = calc_avg([1245.2, 1252.1, 1247.8])
avg_c = calc_avg([1251.0, 1249.5, 1250.8])
print(avg_a, avg_b, avg_c)
```
看,不用再写 `sum(data) / len(data)` 了。每次要算均值,敲一行 `calc_avg()` 就完事。
同样,算标准差也打包:
```python
def calc_std(data):
"""计算样本标准差"""
avg = sum(data) / len(data)
variance = sum((x - avg)**2 for x in data) / (len(data) - 1)
return variance ** 0.5
print(calc_std([1250.5, 1248.3, 1251.2]))
```
现在要改什么? 只需要改 calc_avg 或 calc_std 里面的一行,所有用到它们的地方自动生效。
2.2 参数帮你"调出不同结果"
函数可以接受参数,让同样的算法适应不同的需求:
```python
def calc_avg(data, rounding=1):
"""
计算平均值,支持指定小数位
参数:
data: 数据列表
rounding: 保留几位小数(默认1位)
"""
raw = sum(data) / len(data)
return round(raw, rounding)
不同精度需求
print(calc_avg([1250.5, 1248.3])) # 保留1位: 1249.4
print(calc_avg([1250.5, 1248.3], 3)) # 保留3位: 1249.400
```
2.3 return和print有什么区别?
```python
❌ 错误:只用print,结果不能用
def calc_avg_bad(data):
print(sum(data) / len(data)) # 只是打印出来,没法赋值
result = calc_avg_bad([1, 2, 3]) # 打印 2.0
print(result) # None —— 空!因为没有return
✅ 正确:用return返回
def calc_avg_good(data):
return sum(data) / len(data) # 返回结果
result = calc_avg_good([1, 2, 3]) # result = 2.0
print(result * 10) # 20.0 —— 可以继续用
```
核心区别:`print` 是给肉眼看的,`return` 是给代码接着用的。函数一定要有 `return` 才能把结果传给后面的逻辑。
---
三、实战案例:搭建你的数据分析工具模块
3.1 把函数写到另一个文件里
打开IDE,新建一个文件 `fab_stats.py`:
```python
===== fab_stats.py =====
"""FAB数据统计工具包——一次写好到处用"""
def calc_avg(data):
"""计算平均值"""
if len(data) == 0: # 防止除零错误
return 0
return sum(data) / len(data)
def calc_std(data):
"""计算样本标准差"""
n = len(data)
if n < 2:
return 0
avg = calc_avg(data)
variance = sum((x - avg)**2 for x in data) / (n - 1)
return variance ** 0.5
def find_outliers(data, sigma=3):
"""
用3-sigma规则找异常值
参数:
data: 数据列表
sigma: 标准差倍数(默认3,调小更敏感)
返回:
list: 异常值的列表
"""
avg = calc_avg(data)
std = calc_std(data)
ucl = avg + sigma * std
lcl = avg - sigma * std
return [x for x in data if x > ucl or x < lcl]
def cpk(data, usl, lsl):
"""
计算工艺能力指数Cpk
参数:
data: 测量值列表
usl: 规格上限
lsl: 规格下限
"""
avg = calc_avg(data)
std = calc_std(data)
if std == 0:
return 0
cpu = (usl - avg) / (3 * std)
cpl = (avg - lsl) / (3 * std)
return min(cpu, cpl)
```
保存。然后在另一个文件里用:
```python
===== my_analysis.py =====
from fab_stats import calc_avg, calc_std, find_outliers, cpk
读取一批膜厚数据
thickness = [1250.5, 1248.3, 1251.2, 1249.8, 1250.1,
1247.5, 1250.8, 1249.1, 1251.5, 1248.2]
avg = calc_avg(thickness)
std_val = calc_std(thickness)
bad_vals = find_outliers(thickness)
c = cpk(thickness, usl=1253, lsl=1247)
print(f"均值={avg:.2f}, 标准差={std_val:.2f}")
print(f"异常值: {bad_vals}")
print(f"Cpk={c:.3f}")
```
3.2 再加个条件判断——区分异常Lot
```python
def check_lot(thickness_data, lot_id="Unknown"):
"""
判断一批Lot是否合格
返回:
dict: 包含所有分析指标的字典
"""
avg = calc_avg(thickness_data)
std = calc_std(thickness_data)
outliers = find_outliers(thickness_data)
c = cpk(thickness_data, usl=1253, lsl=1247)
# 判定标准:没有异常值 且 Cpk >= 1.0
passed = (len(outliers) == 0) and (c >= 1.0)
return {
"lot_id": lot_id,
"avg": avg,
"std": std,
"outlier_count": len(outliers),
"cpk": c,
"pass": passed
}
批量检查
all_lots = [
{"id": "A001", "data": [1250.5, 1248.3, 1251.2]},
{"id": "A002", "data": [1245.2, 1252.1, 1200.0]}, # 有异常
{"id": "A003", "data": [1251.0, 1249.5, 1250.8]},
]
for lot in all_lots:
result = check_lot(lot["data"], lot["id"])
status = "✅ 合格" if result["pass"] else "⚠ 不合格"
print(f"{result['lot_id']}: {status} "
f"(平均={result['avg']:.1f}, "
f"异常={result['outlier_count']}个, "
f"Cpk={result['cpk']:.2f})")
```
---
四、效果对比
| 对比维度 | 复制粘贴 | 用函数+模块 | 提升 |
|---------|---------|------------|------|
| 分析50个Lot | 复制50次(40分钟) | 1次循环(2秒) | 1200倍 |
| 改计算逻辑 | 改50处(30分钟) | 改1处(1分钟) | 30倍 |
| 代码行数 | 800+行 | 60行 | -92% |
| 调试难度 | 找bug像大海捞针 | 每个函数可单独测试 | 省心 |
| 新项目复用 | 重写 | `from fab_stats import *` | 秒用 |
---
五、自己动手
```python
练习:把你的工序数据做成模块
步骤1:新建一个 fab_thickness.py 文件
在里面写上:
def calc_avg(data): ...
def calc_range(data): ... # 计算极差 max - min
def check_thickness(data, target, tolerance): ...
步骤2:在另一个文件里 import 它
from fab_thickness import check_thickness
步骤3:批量检查一批数据
```
思考题:
1. 为什么 `return` 比 `print` 重要?试试把函数里的return改成print,看看能不能赋值
2. 模块文件里的函数怎么加注释?加上 `"""文档字符串"""` 试试
3. 如果模块文件和主文件不在同一个文件夹,怎么import?(提示:用 `sys.path` 或者建包)
---
六、常见误区
| 错误 | 后果 | 解决方法 |
|------|------|---------|
| 函数名和变量名重复 | 变量覆盖了函数 | 用不同名字 |
| 忘了return | 返回None | 每个函数确保有return |
| 全局变量改来改去 | 改到哪乱了都不知道 | 尽量用参数传值 |
| 一个函数做太多事 | 测试困难,修改麻烦 | 一个函数只做一件事 |
一句话总结:看到重复代码3次以上,立刻改成函数。
---
> 💬 你那有没有"复制粘贴到崩溃"的经历?评论区聊聊
> 📚 收藏+点赞,下一篇讲文件读写——把数据存起来 👆





