本节摘要:报错不是敌人,是厨房的烟雾报警器——读懂它,翻车变复盘。本节整理数据处理最高频的报错家族(KeyError、ValueError、TypeError 等)的典型病因,给出批处理循环里 try 与 except 的正确姿势、数据质量断言的用法,以及最小复现的调试纪律。承接 8.3 的多灶现场,通往 8.5 的流水线收官。
最初独立写清洗脚本的那段日子,几乎每晚会撞上同几类报错:KeyError 抱怨找不到列,ValueError 嫌弃转不动的值,TypeError 拒绝把文本和数字相加——而且常常修好一处,三行之后又冒一处。后来才明白:报错信息的措辞是稳定的,病因是有限的,按家族对号入座,排错就从玄学变成查表。本节把这张表交给你,再配上批处理现场的容错写法与断言闸门。往前接 8.3 的并行现场(多灶出错更要会读报错),往后 8.5 的流水线里,每一站都会装上本节的报警器。
KeyError:按键取不到——列名拼错、大小写不一致、merge 后列被加后缀、groupby 后迭代键解包错误;处置先 print(df.columns.tolist()) 看真实列名。ValueError:值本身不合格——astype 转不动的脏值、pivot 撞上重复索引、广播形状对不上;处置按 2.4 的 coerce 思路把问题值显式化。TypeError:类型的运算不成立——文本加数字、对非字符串列点 str 访问器、把 Series 当布尔用 and;处置回 1.1 验收类型。IndexError 与 IndexError 的近亲:按位置取超界,常出在 iloc 与空表上——取数前先判 len。SettingWithCopyWarning:链式索引写不进去(3.1 的老对头),处置改成一次性 loc。MemoryError:内存见底,处置翻 8.2 的三招。
import pandas as pd df = pd.DataFrame({"金额": ["120", "88", "待定"]}) # 容错的批量转换:出错的具体行被点名,而不是整段崩掉 def to_num_safe(s: pd.Series) -> pd.Series: return pd.to_numeric(s, errors="coerce") try: df["金额"] = to_num_safe(df["金额"]) except Exception as exc: # 兜底闸:真有意外时留痕 print(f"转换失败:{exc!r}") # 记录后继续或终止,按业务定 finally: print(f"当前缺失数:{df['金额'].isna().sum()}") # 无论成败都体检
场景:8.3 并行清洗的串行版本——一百个文件逐个处理,个别文件坏掉不能拖垮全场,坏的要记名、要能重跑。这是 try、except、finally 的标准战场。
import glob, traceback bad_log = [] results = [] for path in glob.glob("stores/*.csv"): try: df = pd.read_csv(path) assert "金额" in df.columns, f"缺金额列:{path}" # 质量闸门 results.append({"文件": path, "总额": df["金额"].sum()}) except Exception as exc: bad_log.append({"文件": path, "错误": repr(exc)}) # 记名不吞错 print(f"成功 {len(results)} 个,失败 {len(bad_log)} 个") # 失败清单留档,修好上游后只重跑 bad_log 里的文件
报错一长串看不懂时,走固定三步。第一步,抽最小数据:df.sample(20) 取小样本重跑出错代码——大量"大数据才出现"的报错,在小样本上立刻露出真身,还能秒级重试。第二步,二分定位:把可疑的处理链从中间砍断,确认错在前半段还是后半段,逐步缩小到具体一行。第三步,对照类型:错定位后,拿报错名回上面的家族对照表查病因,再进对应章节翻参数。三步练熟,多数排错从"一下午"缩到"一杯咖啡"。
sample = df.sample(20, random_state=0) # 第一步:最小复现 # 第二步:只跑第三站清洗,确认错误不在前两站 cleaned = sample.drop_duplicates(subset=["订单号"])
调试的本质是"把不确定的范围折半再折半"——三步法只是把这个思想固定成了动作。
**翻车一:裸 except 吞错。**except: pass 一刀切,所有异常静默消失——脚本"正常跑完",结果一半数据没处理。容错的底线是 except 至少要记日志(如示例的 bad_log),让失败可见。**翻车二:try 的范围太大。**把整段脚本包进一个 try,出了错只知道"某处错了"——try 的边界要画在单个最小操作上(读一个文件、转一列),报错才能精确定位。**翻车三:把 assert 当业务校验。**assert 语句在优化模式下会被整体跳过——线上校验数据质量要用显式的 if 加 raise,assert 只适合开发期的自检。翻车四:修症状不修病根。"报错就跳过这一行"跑了半年,没人问为什么那一行总是脏的——bad_log 要定期回看,反复出现同一类失败,说明上游流程有病根,转 8.5 的流水线治理。
print 调试之外的正规军是日志库(logging 模块):分级记录、可开关、可落文件,批处理脚本的标配;交互式排查用 Notebook 的单元格逐段跑,配合 3.1 的抽样切片快速缩小范围;复杂的逻辑错误用断点调试器单步走。而"防错"的最上游其实是 1.1 的验收单——大多数 KeyError 与 TypeError,在入口处跑一遍 dtype 与缺失体检就能提前拦住。数据质量的高频挑战(编码乱码、列名带空格、合并键不一致),处置套路都归位在对应章节:乱码看读入的编码参数,列名用 strip 加改名统一,键不一致回 2.3 与 5.1。
排错手册备齐,最后一站:8.5 把八章手艺串成流水线,用一份真实的脏数据从头跑到尾。