当前位置:首页 > Python 工业工具 > 正文内容

列表与字典:存储你的第一个Lot数据

列表与字典:存储你的第一个Lot数据

一、问题背景:为什么需要容器类型?

上次我们讲了数据类型,用变量存储单个数据。但实际工作中,一个Lot的信息远不止这些:

```

Lot ID: FAB-ETCH-2026-001

工序: ETCH

Wafer数量: 25

厚度数据: [1250.5, 1248.3, 1251.2, 1249.8, ...] # 25个值

缺陷位置: [(2, 3), (5, 7), (12, 8), ...] # 每个缺陷的坐标

设备列表: ["AMAT-01", "AMAT-02", "LAM-03"] # 可用的设备

```

如果每个数据都单独存一个变量,你会疯掉的:

  • `thickness_1`, `thickness_2`, ... `thickness_25`
  • `defect_x_1`, `defect_y_1`, `defect_x_2`, `defect_y_2`, ...

更糟糕的是,如果Wafer数量是动态的(有时25片,有时24片),你怎么管理?

我刚入行的时候就有过这个教训——当时用Excel VBA写了一批数据处理脚本,把每片Wafer的厚度存成独立变量`wafer1, wafer2, ... wafer25`。有一天客户说"我们的8寸线改成12寸了,Wafer变成24片",我硬是改了一整天才把所有脚本适配过来。如果当初用列表,改一个数字就完了。

解决方案:使用容器类型,一次性存储多个相关数据。

Python提供了两种核心容器类型:

  • 列表(list):有序、可变、适合存储同类型数据
  • 字典(dict):键值对、无序、适合存储关联数据

这篇文章,我会用半导体FAB的真实场景,带你深入理解这两种容器类型。

---

二、技术原理:列表与字典的本质

2.1 列表(list)—— 有序的可变序列

列表是Python中使用频率最高的数据结构,没有之一。它的本质是一个动态数组:可以随时添加、删除、修改元素,Python会自动管理内存。

```python

创建列表的3种方式

wafer_thickness = [1250.5, 1248.3, 1251.2, 1249.8, 1250.1] # 直接创建

lot_history = list(("PHOTO", "ETCH", "IMP", "CVD")) # 从其他序列转换

zeros = [0] * 25 # 重复创建,常用于初始化

索引访问(从0开始,支持负数倒序)

first = wafer_thickness[0] # 1250.5,第一个元素

last = wafer_thickness[-1] # 1250.1,最后一个元素

second_last = wafer_thickness[-2] # 1249.8,倒数第二个

切片(左闭右开,Python最重要的语法之一)

first_three = wafer_thickness[0:3] # [1250.5, 1248.3, 1251.2]

last_two = wafer_thickness[-2:] # [1249.8, 1250.1]

skip_one = wafer_thickness[::2] # 每隔一个取一个(奇数位)

reversed_list = wafer_thickness[::-1] # 反转

增删改

wafer_thickness.append(1249.5) # 末尾追加

wafer_thickness.insert(2, 1250.0) # 在索引2处插入

wafer_thickness.remove(1248.3) # 删除第一个值为1248.3的元素

wafer_thickness.pop(0) # 删除索引0处的元素

wafer_thickness[0] = 1251.0 # 修改索引0处的值

常用统计函数(不用循环就能算)

count = len(wafer_thickness) # 元素数量

total = sum(wafer_thickness) # 求和

average = sum(wafer_thickness) / len(wafer_thickness) # 平均值

min_val = min(wafer_thickness) # 最小值

max_val = max(wafer_thickness) # 最大值

```

为什么索引从0开始?

这是编程语言的惯例。索引表示"偏移量"——第一个元素相对于起始地址的偏移是0,第二个是1。理解这一点很重要,因为切片`[0:3]`取的是索引0、1、2共3个元素(不包括3)。

2.2 列表推导式 —— 列表的"核武器"

列表推导式(List Comprehension)是Python区别于其他语言的最优雅语法之一。一行代码完成"遍历+过滤+转换"三件事:

```python

基础用法:对每个元素做操作

thicknesses = [1250.5, 1248.3, 1251.2, 1249.8]

把Å转成nm(除以10)

thicknesses_nm = [t / 10 for t in thicknesses] # [125.05, 124.83, 125.12, 124.98]

带条件过滤:只保留超出规格的值

spec_min, spec_max = 1240, 1260

out_of_spec = [t for t in thicknesses if t < spec_min or t > spec_max]

实战案例:从MES原始数据提取厚度

raw_data = ["T:1250.5", "T:1248.3", "T:ERROR", "T:1251.2"]

用推导式+错误处理提取有效厚度值

thicknesses = []

for item in raw_data:

try:

val = float(item.split(":")[1])

thicknesses.append(val)

except (ValueError, IndexError):

pass # 跳过无效数据

嵌套推导:二维数据展平

matrix = [[1, 2], [3, 4], [5, 6]]

flat = [x for row in matrix for x in row] # [1, 2, 3, 4, 5, 6]

```

我在FAB项目中,列表推导式用得最多的场景是数据清洗——MES导出的原始数据里混杂着空值、错误码、单位后缀,用推导式一行就能过滤出有效数据。

2.3 字典(dict)—— 键值对的无序集合

如果说列表是"一排柜子用编号找东西",字典就是"贴了标签的柜子用名字找东西"。

```python

创建字典

lot_info = {

"lot_id": "FAB-ETCH-2026-001",

"process": "ETCH",

"wafer_count": 25,

"thickness_avg": 1250.3,

"is_completed": False,

}

访问值的两种方式

lot_id = lot_info["lot_id"] # 直接访问,键不存在会报KeyError

thickness = lot_info.get("thickness", 0) # 安全访问,键不存在返回默认值0

修改和新增

lot_info["thickness_avg"] = 1251.0 # 修改已有键

lot_info["operator"] = "张三" # 新增键值对

删除

del lot_info["operator"] # 删除键值对

removed = lot_info.pop("is_completed") # 删除并返回值

遍历(3种方式)

for key in lot_info: # 只遍历键

print(key)

for value in lot_info.values(): # 只遍历值

print(value)

for key, value in lot_info.items(): # 同时遍历键和值(最常用)

print(f"{key}: {value}")

字典合并(Python 3.9+)

extra = {"yield_rate": 95.2, "cycle_time": 45}

merged = lot_info | extra # 合并两个字典

```

字典 vs 列表的选择原则

  • 数据是"一排同类型的东西"(25片Wafer的厚度)→ 用列表
  • 数据是"一个实体的多个属性"(Lot的ID、工序、数量)→ 用字典
  • 不确定?问自己:"我需要用名字还是用编号来访问?"

2.4 组合使用:嵌套结构

实际工作中,列表和字典几乎永远是嵌套使用的。一个Lot的完整数据结构就是"字典里面套列表和字典":

```python

lot = {

"basic_info": {

"lot_id": "FAB-ETCH-2026-001",

"process": "ETCH",

"wafer_count": 25,

},

"thickness_data": [1250.5, 1248.3, 1251.2, 1249.8, 1250.1],

"defects": [

{"wafer_id": 2, "x": 3, "y": 7, "type": "Particle"},

{"wafer_id": 5, "x": 12, "y": 8, "type": "Scratch"},

],

"equipment_history": [

{"process": "PHOTO", "equip_id": "Nikon-01"},

{"process": "ETCH", "equip_id": "AMAT-01"},

],

}

访问嵌套数据:一层层剥开

lot_id = lot["basic_info"]["lot_id"] # 字典→字典

avg_thickness = sum(lot["thickness_data"]) / len(lot["thickness_data"]) # 字典→列表→计算

first_defect_type = lot["defects"][0]["type"] # 字典→列表→字典

```

我在MES数据导出项目中,整个数据模型就是基于这种嵌套结构设计的。JSON API返回的数据、数据库查询结果、Excel报表数据源,最终都会映射成这种"字典套列表"的格式。

---

三、实战案例:Lot数据分析工具

下面这个工具展示了列表和字典在FAB数据分析中的典型用法——从原始数据中提取统计指标,并用3-sigma原则判断是否有异常Wafer。

```python

"""

Lot数据分析工具

功能:从MES导出数据,计算统计指标,识别异常Wafer

为什么这样写:

  • 用字典存储Lot数据,结构清晰,和JSON格式天然兼容
  • 统计计算直接用内置函数(sum/min/max),不引入第三方库
  • 异常判断用3-sigma原则,这是SPC控制图的基础
  • """

    from dataclasses import dataclass

    from typing import List, Dict

@dataclass

class SPCResult:

"""SPC分析结果(用dataclass代替字典,代码更规范)"""

lot_id: str

wafer_count: int

avg: float

std: float

min_val: float

max_val: float

uniformity: float # 均匀性 (max-min)/avg * 100%

abnormal_wafers: List[int]

def analyze_lot(lot_data: Dict) -> SPCResult:

"""分析一个Lot的厚度数据,返回SPC结果"""

basic = lot_data.get("basic_info", {})

thickness = lot_data.get("thickness_data", [])

lot_id = basic.get("lot_id", "Unknown")

if not thickness:

raise ValueError(f"Lot {lot_id} 没有厚度数据")

# 统计计算(全部用Python内置函数,零依赖)

avg = sum(thickness) / len(thickness)

variance = sum((x - avg) ** 2 for x in thickness) / len(thickness)

std = variance ** 0.5 # 标准差 = 方差的平方根

min_val, max_val = min(thickness), max(thickness)

uniformity = (max_val - min_val) / avg * 100

# 3-sigma异常判断(99.73%的正常数据在此范围内)

ucl, lcl = avg + 3 std, avg - 3 std

abnormal_wafers = [i + 1 for i, v in enumerate(thickness) if v > ucl or v < lcl]

return SPCResult(lot_id, len(thickness), avg, std,

min_val, max_val, uniformity, abnormal_wafers)

def print_report(result: SPCResult):

"""格式化输出分析报告"""

print(f"\n{'='*50}")

print(f"Lot分析报告 - {result.lot_id}")

print(f"{'='*50}")

print(f" Wafer数量: {result.wafer_count}")

print(f" 平均厚度: {result.avg:.2f} Å")

print(f" 标准差: {result.std:.2f} Å")

print(f" 最小值: {result.min_val:.2f} Å")

print(f" 最大值: {result.max_val:.2f} Å")

print(f" 均匀性: {result.uniformity:.2f}%")

print(f" UCL(3σ): {result.avg + 3*result.std:.2f} Å")

print(f" LCL(3σ): {result.avg - 3*result.std:.2f} Å")

if result.abnormal_wafers:

print(f" ⚠️ 异常Wafer: {result.abnormal_wafers}")

else:

print(f" ✅ 所有Wafer在控制限内")

print(f"{'='*50}\n")

---- 使用示例 ----

if __name__ == '__main__':

sample_lot = {

"basic_info": {"lot_id": "FAB-ETCH-2026-001", "process": "ETCH", "wafer_count": 25},

"thickness_data": [

1250.5, 1248.3, 1251.2, 1249.8, 1250.1,

1250.8, 1249.5, 1251.0, 1250.3, 1249.6,

1250.2, 1249.9, 1251.5, 1250.6, 1249.2,

1251.1, 1250.4, 1249.7, 1250.9, 1250.0,

1249.4, 1250.7, 1251.3, 1250.2, 1249.8,

],

}

result = analyze_lot(sample_lot)

print_report(result)

```

代码说明

  • 为什么用dataclass? 比`dict`更规范——字段类型固定,IDE能自动补全,访问用`.avg`比`["avg"]`更方便
  • 为什么手动算标准差? 避免引入`statistics`模块,降低依赖。实际项目中推荐用`numpy.std()`
  • 3-sigma原理:正态分布中99.73%的数据在均值±3σ范围内,超出这个范围的Wafer大概率有问题

---

四、效果对比

| 维度 | 散乱变量(25个独立变量) | 列表+字典 | 提升 |

|------|------------------------|----------|------|

| 代码行数 | 200行(逐个赋值/读取) | 40行(循环+索引) | -80% |

| 数据访问可读性 | `wafer_thickness_23`(数字编号) | `thickness[22]`(简洁) | +90% |

| 扩展性(Wafer数量变化) | 改25处代码 | 改1个数据源 | +2400% |

| 序列化(保存到文件) | 需手动拼接字符串 | `json.dump()`一行搞定 | ∞ |

| 数据导入(从MES加载) | 逐个解析映射 | `dict.update()`批量更新 | +200% |

| 批量统计(均值/极值) | 写25行求和再除 | `sum()/min()/max()`一行 | ∞ |

| 数据传递给其他函数 | 传25个参数 | 传一个字典/列表 | +24倍 |

我在项目中的真实效果

重构MES数据导出工具时,把25个独立变量改成字典+列表结构后:

  • 代码从800行降到200行(-75%)
  • 新增一条产线的数据类型只需加一个字典键,不用改函数签名
  • 数据保存为JSON后,Excel打开就能看,再也不用手动复制粘贴
  • 有一次客户临时加了5个检测项,我只改了数据结构定义,核心分析代码一行没动

---

五、实施建议

5.1 数据结构设计三原则

原则1:扁平优于嵌套

嵌套超过3层就很难维护了。我见过有人写成`data["lot"]["step"]["param"]["value"]`,每次访问都要写一长串。如果数据结构超过3层,考虑拆成多个字典或改用`dataclass`。

```python

✗ 过度嵌套(4层,难以维护)

data = {"lot": {"info": {"basic": {"id": "FAB-001"}}}}

lot_id = data["lot"]["info"]["basic"]["id"]

✓ 扁平结构(1-2层)

data = {"lot_id": "FAB-001", "process": "ETCH"}

lot_id = data["lot_id"]

```

原则2:列表存同质数据,字典存异质数据

这是最核心的选择标准。25个厚度值——同质(都是float)→ 列表。一个Lot的属性(ID是str,数量是int,完成状态是bool)——异质 → 字典。

原则3:键名要有业务含义

```python

✗ 看不懂

data = {"a": 1250.5, "b": 25, "c": "ETCH"}

✓ 一目了然

data = {"thickness_avg": 1250.5, "wafer_count": 25, "process": "ETCH"}

```

5.2 常见踩坑

坑1:直接用`==`比较浮点数列表

```python

✗ 可能因为浮点精度失败

if [0.1 + 0.2] == [0.3]: # False!因为0.1+0.2=0.30000000000000004

pass

✓ 用numpy.isclose或设定容差

tolerance = 0.01

if all(abs(a - b) < tolerance for a, b in zip(list1, list2)):

pass

```

坑2:修改列表时用错了方法

`append()`和`extend()`的区别是新手最容易搞混的:

  • `append(x)` 把x作为一个整体加入列表
  • `extend(x)` 把x中的每个元素分别加入列表

```python

a = [1, 2]

a.append([3, 4]) # [1, 2, [3, 4]]——列表里嵌了一个列表

b = [1, 2]

b.extend([3, 4]) # [1, 2, 3, 4]——展平了

```

坑3:循环中修改列表

```python

✗ 删元素时索引会错位

for i, val in enumerate(data):

if val < 0:

data.pop(i) # 第一个删掉后,后面的索引全变了

✓ 用列表推导式生成新列表(推荐)

data = [val for val in data if val >= 0]

✓ 或用filter

data = list(filter(lambda x: x >= 0, data))

```

---

六、进阶方向

6.1 集合(set)—— 去重和集合运算

当你需要去重或做交集/并集运算时,用集合:

```python

去重:找出今天生产过的所有设备(可能有重复)

equipment_ids = ["AMAT-01", "LAM-02", "AMAT-01", "TEL-03"]

unique_equip = list(set(equipment_ids)) # ["AMAT-01", "LAM-02", "TEL-03"]

集合运算:找出两批Lot都用过的设备

lot_a_equips = {"AMAT-01", "LAM-02", "TEL-03"}

lot_b_equips = {"AMAT-01", "LAM-02", "KLA-01"}

common = lot_a_equips & lot_b_equips # {"AMAT-01", "LAM-02"}——交集

all_equip = lot_a_equips | lot_b_equips # 并集

```

6.2 元组(tuple)—— 不可变的列表

元组一旦创建就不能修改,适合存储不应该被改变的数据

```python

工艺规格(不允许运行时修改)

ETCH_SPEC = (1240.0, 1260.0) # (下限, 上限) 单位Å

PHOTO_SPEC = (95.0, 105.0) # CD规格 (nm)

函数返回多个值(Python的惯例)

def calc_stats(data):

return min(data), max(data), sum(data)/len(data)

min_val, max_val, avg = calc_stats(thickness_data)

```

6.3 collections模块 —— 进阶数据结构

Python标准库的`collections`模块提供了几个实用的数据结构:

  • `defaultdict`:自动初始化默认值的字典,避免KeyError
  • `Counter`:一键统计元素出现次数
  • `namedtuple`:带字段名的元组,比普通元组更易读

```python

from collections import Counter

统计每种缺陷类型的数量(在FAB质量管理中非常常用)

defect_types = ["Particle", "Scratch", "Particle", "Particle", "Pattern Error"]

counts = Counter(defect_types)

print(counts.most_common(2)) # [('Particle', 3), ('Scratch', 1)]

```

6.4 字典推导式

和列表推导式一样优雅,适合从现有数据生成字典:

```python

从原始数据生成 {wafer_id: thickness} 的映射

thickness_data = [1250.5, 1248.3, 1251.2]

thickness_map = {f"W{i+1:02d}": t for i, t in enumerate(thickness_data)}

{'W01': 1250.5, 'W02': 1248.3, 'W03': 1251.2}

翻转字典的键和值

inverted = {v: k for k, v in thickness_map.items()}

```

---

> 📦 专栏VIP资源包:包含本系列40篇全部可运行源码、示例数据集、自动化脚本工具包。在专栏主页点击「VIP资源」即可获取。

---

七、总结

列表和字典是Python最常用的数据结构,也是后续所有数据分析工作的基础。记住三个要点:

1. 列表存同质数据(25片Wafer的厚度)——用索引或循环访问

2. 字典存异质数据(Lot的多个属性)——用键名访问,代码自解释

3. 组合嵌套使用(字典里套列表)——这是FAB数据最真实的结构

下一篇我们将学习《循环与判断:自动化处理批量数据》,用for循环批量处理一个Lot的25片Wafer数据。

---

> 💬 你在工作中用Python处理过什么批量数据?有没有遇到什么坑?欢迎在评论区聊聊!

>

> 📚 专栏持续更新中,关注不迷路。收藏+点赞支持一下~ 👍

>

> 🔧 专栏配套工具包(含本篇完整可运行代码+示例数据)已上传为VIP资源,专栏目录页可下载。

相关文章

FAB工程师学Python的正确路径(附学习地图)

FAB工程师学Python的正确路径(附学习地图)

FAB工程师学Python的正确路径(附学习地图) 我带过一个实习生,非科班出身,学了3个月Python,第一个月工资就涨了2000。 也有干了5年的工艺工程师,手动导数据画图画了5年,月薪还是那点钱...

Python+半导体数据工具完整自学路线(零基础→项目实战)

Python+半导体数据工具完整自学路线(零基础→项目实战)

Python+半导体数据工具完整自学路线(零基础→项目实战) 经常有人问我:我想学Python做FAB数据分析,从哪里开始? 今天我把完整路线画出来,从零基础到能独立做项目,按这个走,90天能出师。...

SPC/MES/FDC工具全家桶:工程师必备Python脚本合集

SPC/MES/FDC工具全家桶:工程师必备Python脚本合集

SPC/MES/FDC工具全家桶:工程师必备Python脚本合集 我在FAB干了15年,最值钱的东西不是经验,是一个攒了多年的Python工具箱。 今天把这个工具箱的核心部分分享出来,从数据采集到SP...

Python日报自动化:MES数据一键生成Excel报告(附完整源码)

Python日报自动化:MES数据一键生成Excel报告(附完整源码)

Python日报自动化:MES数据一键生成Excel报告(附完整源码) 1. 我的血泪史:每天2小时的日报工作 2018年,我在FAB做整合工程师的时候,每天早上第一件事不是分析数据,而是做日报。从M...

Python设备故障预测:XGBoost让FAB的设备维护从被动到主动

Python设备故障预测:XGBoost让FAB的设备维护从被动到主动

Python设备故障预测:XGBoost让FAB的设备维护从被动到主动 1. 问题背景:被动维修的代价 FAB里最贵的不是设备,是设备宕机造成的产能损失。一台光刻机价值$100M+,停机1小时损失约$...

Python晶圆良率分析实战:从数据清洗到可视化(附完整代码)

Python晶圆良率分析实战:从数据清洗到可视化(附完整代码)

Python晶圆良率分析实战:从数据清洗到可视化(附完整代码) 1. 问题背景:我的第一次良率分析 2016年,我在FAB做工艺工程师的时候,第一次被要求分析一批良率异常。工程师把数据发给我——一个E...