——以“契约先行”为主线的脏数据加载工程方法论
编码探测 · 列名归一 · 类型推断 · 提示词工程 · 可验证交付
摘要
企业数据湖里最普遍的“脏”不是缺失值,而是编码与命名的双重混乱:同一批CSV里GBK与UTF-8交错,同一张表里“销售日期”与“sale_date”并存。直接让大模型“写个pandas读取代码”,得到的往往是能在示例数据上跑通、却在真实文件上抛UnicodeDecodeError或KeyError的“演示级”代码。
本文提出一条贯穿全文的独创分析主线——“契约先行”(Contract-First):在提示AI之前,先把“文件如何被识别、列名如何被映射、类型如何被约束”写成机器可校验的契约,再让AI在契约边界内生成代码。围绕这条主线,文章依次拆解编码探测的字节级原理、列名归一的规范化算法、类型推断的失败模式,以及把契约注入提示词的具体模板与迭代策略,并给出可落地的校验脚本与工程化路径。
本文评述:把“提示词技巧”降格为“契约表达”,是让AI从“会写代码”走向“写出可用代码”的关键分水岭;提示词的本质不是咒语,而是需求的形式化规格说明。
目录
一、问题的本质:为什么AI写的加载代码“一跑就崩”
先看一个几乎每个数据工程师都遇到过的场景。你把一份从业务系统导出的CSV丢给大模型,附上一句“帮我写段Python读取这个文件并做基础清洗”。模型很快返回一段结构清晰的pd.read_csv(path),附带dropna()和几行列名重命名。你把代码粘进Notebook,回车——第一行就炸了。
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd6 in position 0: invalid continuation byte
这个报错信息里的0xd6是理解整篇文章的钥匙。在GBK编码中,0xd6 0xd0两个字节合起来表示汉字“中”;而在UTF-8的解码器眼里,0xd6是一个“续字节”的起始标志,后面必须跟特定范围的字节,于是直接判定非法。换句话说,同一个字节序列,在不同编码假设下会得到完全不同的解释结果,而解码器默认不会“猜”,它只会按你给的规则硬解。
1.1 三类典型崩溃,其实同源
把真实项目里的失败案例归类,会发现它们高度收敛于三类:
表面看是三个问题,本质是同一个:AI在生成代码时,对输入数据的“隐含假设”没有被显式化。模型默认文件是UTF-8、列名是英文、日期是ISO格式——这些假设在训练语料的“干净示例”里成立,在真实企业数据里几乎必然失效。
1.2 为什么“多试几次”解决不了问题
很多人的第一反应是:报错了就把报错贴回去,让AI改。这个策略在简单bug上有效,但在编码与列名问题上会陷入“打地鼠”:改了编码,列名又错;改了列名,日期解析又崩。原因在于,这些约束是相互耦合的,而对话式修补是局部、逐条的。每一次修补只针对上一个报错,模型并不知道全局还有多少坑没踩。
本文评述:把失败归因于“模型不够聪明”是一种懒惰的归因。更准确的判断是——提示词没有承载足够的形式化约束,模型只能在信息缺失下做最大似然猜测。要让它写对,先要让它“知道得足够多”,而“知道”的载体不是自然语言描述,而是结构化的契约。
1.3 一个被忽视的事实:编码问题在中文数据里是“常态”而非“例外”
GBK(及其超集GB18030)作为国家标准GB2312的扩展,长期存在于国内政务、金融、制造、医疗等行业的遗留系统中。大量业务系统导出的报表、Excel另存的CSV、老OA系统的接口文件,默认编码仍是GBK系列。与此同时,新建设的互联网系统、云原生应用、开源工具链默认UTF-8。两套体系在数据湖里汇合,就形成了“同一目录下不同文件编码不同、甚至同一文件不同行编码不同”的混合态。
国家标准GB18030-2022《信息技术 中文编码字符集》的实施,进一步强化了中文编码的强制性要求,但存量系统的迁移是长周期的。这意味着,在未来相当长一段时间内,“编码混用”是数据接入层必须正面处理的工程现实,而不是可以绕过的边缘情况。关于GB18030的强制性国家标准全文,可在全国标准信息公共服务平台(std.samr.gov.cn)检索查阅。
二、分析主线:契约先行(Contract-First)的四层结构
本文提出的核心方法论是“契约先行”。它的直觉很简单:与其让AI在生成代码时“猜”数据长什么样,不如先把数据的结构、编码、命名、类型写成一份可校验的契约,再让AI在契约的约束下生成代码。契约是人与AI之间的接口协议,也是代码正确性的判据。
2.1 四层契约的划分
把加载过程拆解,可以识别出四个必须显式化的决策点,对应四层契约:
这四层不是并列的,而是有依赖顺序:编码错了,列名读出来就是乱码,映射无从谈起;列名错了,类型约束就落不到正确的列上。因此契约必须按L1→L2→L3→L4的顺序逐层构建,提示词也应按这个顺序组织。
2.2 契约的载体:从自然语言到结构化描述
契约最终要“喂”给AI,载体形态直接影响效果。实践中有三种载体,可靠性依次递增:
- 纯自然语言描述:如“文件可能是GBK也可能是UTF-8,列名有中文有英文”。信息密度低,模型容易遗漏。
- 半结构化清单:用Markdown表格或YAML列出列名映射与类型。信息密度高,模型理解稳定。
- 可执行规格:直接给出JSON Schema或pandera/Pydantic模型定义。模型不仅能理解,还能生成与规格一致的校验代码。
本文评述:第三种载体是“契约先行”的理想形态。当契约本身是机器可读的,它同时服务于两个目的——约束AI生成,以及校验AI产出。一份契约,两处复用,这是它优于“提示词技巧”的根本原因。pandera作为pandas生态的schema校验库,其DataFrameSchema设计正是这种思路的工程化体现,文档见pandera.readthedocs.io。
三、第一层契约:编码探测的字节级原理与工程实现
编码契约的目标是:给定一个字节流,确定用哪种编码解码,以及在无法确定时的回退策略。要做好这件事,必须理解编码探测为什么“不可能100%准确”,以及工程上如何把不确定性控制在可接受范围。
3.1 编码探测的本质是统计推断,不是精确判定
一个字节序列可能同时是多种编码下的合法序列。比如字节0xC4 0xE3,在GBK下是“你”,在某些单字节编码下是两个带音标的拉丁字符。探测算法(如Mozilla的chardet、Google的cchardet、Python的charset-normalizer)本质上是在做基于字符频率与合法性约束的统计推断,输出的是“最可能的编码+置信度”,而非确定答案。
charset-normalizer是chardet的现代替代品,其设计文档明确讨论了多编码共存下的检测策略,项目地址见github.com/Ousret/charset_normalizer。本文评述:把探测结果当作“概率建议”而非“事实”,是编码契约设计的第一原则。凡是把探测结果直接硬编码为encoding=detected的代码,都在赌置信度,而赌输的代价是整批数据不可用。
3.2 工程上可靠的三段式策略
结合实践,笔者推荐“显式声明→探测回退→逐行容错”的三段式策略,并按优先级执行:
def decode_bytes(raw: bytes, declared: str | None = None) -> tuple[str, str]:
"""返回 (解码文本, 实际使用编码)。策略:声明优先→探测→回退链。"""
candidates = []
if declared:
candidates.append(declared)
# 探测:取前若干字节做统计推断
guess = charset_normalizer.from_bytes(raw[:65536]).best()
if guess and guess.encoding:
candidates.append(guess.encoding)
# 回退链:中文场景下 GB18030 是 GBK 的超集,优先于 GBK
candidates += ["utf-8-sig", "gb18030", "gbk", "big5"]
for enc in dict.fromkeys(candidates): # 去重保序
try:
return raw.decode(enc), enc
except (UnicodeDecodeError, LookupError):
continue
# 最后手段:errors='replace' 并记录告警,保证流程不中断
return raw.decode("utf-8", errors="replace"), "utf-8(replace)"
这段代码有三个值得强调的设计点。第一,把gb18030排在gbk之前:GB18030是GBK的超集,能解码GBK能解的所有内容,且覆盖更多生僻字,用超集做回退更安全。第二,utf-8-sig排在utf-8之前:带BOM的UTF-8文件用utf-8解码会在首列名前面留下\ufeff,导致列名匹配失败,这是极高频的隐形坑。第三,最后用replace兜底并记录:宁可让少量字符变问号,也不让整批任务失败,同时留下可追溯的告警。
3.3 行级混合编码:更隐蔽的坑
比“文件级混用”更麻烦的是“行级混用”。典型场景是:某系统导出时,表头是UTF-8,数据行由另一个GBK模块写入;或者多文件拼接时未统一转码。此时整体按任一编码解码都会在部分行失败。
处理思路是按行切分字节流后逐行探测:先用\n(0x0A)在字节层切分,再对每行独立执行三段式解码。需要注意GBK的双字节中不会出现0x0A作为尾字节,因此按0x0A切分在GBK下是安全的——这一点常被忽略,但它是行级方案成立的前提。关于编码与换行符的字节级细节,可参考Unicode官方技术报告UAX #13(unicode.org/reports/tr13)。
本文评述:行级混合编码是“契约先行”最需要警惕的场景,因为它打破了“一个文件一个编码”的朴素假设。契约里必须显式写明“是否允许行级混合”,若允许,则加载器必须实现逐行探测;若不允许,则应在接入层直接拒绝并告警,而不是让下游猜。
3.4 编码契约的写法
把上述策略写成契约,供提示词引用:
encoding_contract:
file_level: "declared > detected > [utf-8-sig, gb18030, gbk, big5] > utf-8/replace"
line_level_mixed: false # 若为true,加载器须逐行探测
bom_policy: "strip" # 去除BOM,避免列名污染
on_failure: "raise EncodingContractError with file+line+hexdump"
这份契约的价值在于:它把“编码怎么处理”从一个开放问题变成了一个有明确分支和失败语义的规格。AI拿到它,就不需要猜,只需要按分支实现。
四、第二层契约:列名归一的规范化算法与映射表
列名问题比编码问题更“软”,也更难自动化。编码错了会报错,列名错了可能静默通过——代码读到的是None或空列,下游计算出错误结果却不报警。因此列名契约的核心不是“改名”,而是建立可审计的映射关系,并对映射覆盖率做断言。
4.1 列名混乱的四种形态
其中“隐形字符”最阴险。从Excel复制列名、或经过某些中间系统转码后,列名里可能混入全角空格(U+3000)、零宽空格(U+200B)、不间断空格(U+00A0)。这些字符在打印时不可见,但会让df['销售日期']匹配失败。规范化时必须先做字符级清洗。
4.2 规范化算法:先清洗,再归一,最后映射
笔者建议把列名处理拆成三个独立阶段,每阶段可单独测试:
import re, unicodedata
def clean_colname(name: str) -> str:
# 阶段1:字符级清洗——去BOM、零宽字符、各类空白
name = name.replace("\ufeff", "")
name = re.sub(r"[\u200b-\u200f\u2028\u2029]", "", name)
name = name.replace("\u3000", " ").replace("\u00a0", " ")
name = name.strip()
# 阶段2:形态归一——全角转半角、大小写、分隔符统一
name = unicodedata.normalize("NFKC", name)
name = re.sub(r"[\s\-]+", "_", name)
return name.lower()
def map_columns(raw_cols, mapping: dict, strict=True):
cleaned = {clean_colname(c): c for c in raw_cols}
result, missing = {}, []
for std, aliases in mapping.items():
for alias in aliases:
key = clean_colname(alias)
if key in cleaned:
result[std] = cleaned[key]
break
else:
missing.append(std)
if strict and missing:
raise ColumnContractError(f"缺失必需列: {missing}; 实际列: {list(raw_cols)}")
return result
这里的关键设计是把“清洗后的名字”作为匹配键,而不是直接匹配原始名。这样“销售日期 ”“销售日期”“销售 日期”在清洗后都归一到同一个键,映射表只需维护一份别名列表。同时strict=True时缺失列直接抛异常,避免静默错列。
本文评述:列名归一不能追求“全自动智能匹配”。同义异名(“金额”vs“销售额”)涉及业务语义,任何纯算法匹配都可能误判。工程上更稳妥的做法是“规则归一+显式映射表+覆盖率断言”三件套:规则处理形态差异,映射表处理语义差异,断言保证没有漏网之鱼。把不确定性交给人来确认,把确定性交给代码执行。
4.3 映射表的组织与版本化
映射表是列名契约的核心资产,建议以独立配置文件维护,并纳入版本控制:
columns:
sale_date:
aliases: ["销售日期", "sale_date", "saleDate", "成交日期"]
required: true
amount:
aliases: ["金额", "销售额", "成交额", "amount", "amt"]
required: true
region:
aliases: ["区域", "地区", "region"]
required: false
版本化的意义在于:当上游系统改列名时,映射表的变更可追溯、可评审、可回滚。把映射表写死在代码里,是这类项目最常见的维护灾难来源。关于数据契约的版本化管理,dbt的contract机制提供了可借鉴的工程范式,文档见docs.getdbt.com。
五、第三层契约:类型推断、空值与失败模式约束
编码和列名解决后,类型问题浮出水面。pandas的read_csv会自动推断类型,但推断规则在中文数据上经常“好心办坏事”。
5.1 三种高频类型陷阱
陷阱一:前导零丢失。订单号“00123”被推断为整数123,前导零消失,后续关联全部失败。这类列必须显式声明为字符串。
陷阱二:千分位与货币符号。“1,234.56”或“¥1,234”被当作object,求和时报错或静默跳过。需要在加载时声明thousands=','并剥离货币符号。
陷阱三:中文日期格式。“2024年3月5日”“2024/03/05”“20240305”混在同一列。pandas默认解析器对中文日期支持有限,需要显式提供format或自定义解析函数。
关于pandas类型推断的细节与dtype参数用法,官方IO文档有完整说明:pandas.pydata.org/docs/user_guide/io.html。本文评述:类型推断的默认行为是为“通用场景”优化的,而企业数据是“特定场景”。指望自动推断在特定场景下正确,等于把正确性外包给一个不了解你业务的启发式规则。类型契约的意义就是把“特定场景”的知识显式写下来。
5.2 空值语义必须显式声明
“空”在数据里有多种含义:空字符串、空格、NULL、“N/A”、“-”、“未知”、0。pandas默认把空字符串和部分标记识别为NaN,但“-”和“未知”不会。如果业务上“-”表示“不适用”而“未知”表示“缺失”,两者在分析中的处理方式完全不同,混为一谈会导致统计偏差。
因此类型契约里必须有一节专门声明空值语义:
null_semantics:
global_na_values: ["", " ", "NULL", "null", "N/A", "NA"]
column_overrides:
amount: {na_values: ["-"], meaning: "not_applicable"} # 不适用≠缺失
region: {na_values: ["未知"], meaning: "unknown"} # 未知单独标记
keep_default_na: false # 关闭默认推断,完全按契约执行
5.3 失败模式:fail-fast 还是 fail-soft
契约必须回答一个工程问题:当数据违反契约时,加载器应该怎么办?两种策略各有适用场景:
本文评述:策略选择应由数据用途决定,而非由开发者偏好决定。把策略写进契约,意味着这个决策被显式化、可评审,而不是埋在某个try/except里被悄悄决定。实践中推荐默认fail-fast,仅在明确标注的场景下启用fail-soft,并要求fail-soft必须产出违规明细报告。
六、第四层契约:把契约写进提示词——模板与迭代
前三层契约是“内容”,这一层是“表达”。同样的契约,组织方式不同,AI生成代码的可用率差异显著。笔者基于多轮实践,总结出一套稳定的提示词结构。
6.1 提示词的五段式结构
有效的提示词应包含五个部分,顺序固定:
- 角色与目标:一句话说明任务边界,如“你是数据接入工程师,目标是把混合编码、混合列名的CSV安全加载为规范DataFrame”。
- 输入事实:文件路径模式、可能的编码、原始列名样本、数据量级。事实要具体,不要“可能有一些中文列名”这种模糊描述。
- 契约规格:L1-L3的结构化契约(YAML/表格),这是提示词的主体。
- 交付要求:代码必须包含哪些函数、哪些断言、异常类型、日志字段。
- 反例约束:明确禁止的行为,如“禁止使用
errors='ignore'静默吞错”“禁止硬编码单一编码”。
6.2 一个可直接复用的提示词模板
【角色】你是资深数据接入工程师,擅长处理中文脏数据。
【目标】编写 load_sales(path) 函数,把销售CSV安全加载为DataFrame。
【输入事实】
- 文件:/data/sales/*.csv,单文件5万~200万行
- 编码:历史文件GBK,新文件UTF-8,部分带BOM;不允许行级混合
- 原始列名样本:["销售日期","sale_date","金额","销售额 ","区域","region"]
- 已知坑:金额含千分位逗号;日期有"2024年3月5日"和"2024-03-05"两种
【契约】
encoding: 声明>探测> [utf-8-sig, gb18030, gbk] > utf-8/replace,去BOM
columns:
sale_date: {aliases: ["销售日期","sale_date"], required: true, type: datetime}
amount: {aliases: ["金额","销售额"], required: true, type: float, thousands: ","}
region: {aliases: ["区域","region"], required: false, type: string}
null: 全局["","NULL","N/A"];amount的"-"表示不适用,单独标记
failure: fail-fast,抛 DataContractError,含文件名+违规列+样例行
【交付要求】
1) 函数签名 load_sales(path: str) -> pd.DataFrame
2) 必须包含 _detect_encoding、_map_columns、_coerce_types 三个私有函数
3) 每个契约层至少一条 assert,失败信息含具体值
4) 返回前打印:使用编码、列映射结果、行数、各列空值数
【禁止】
- 禁止 errors='ignore'
- 禁止硬编码 encoding='utf-8'
- 禁止用 df.columns 直接赋值而不校验映射覆盖率
这个模板的关键在于“输入事实”和“契约”分离。事实是客观描述,契约是规范要求。模型能清楚区分“世界是什么样”和“代码必须满足什么”,生成时更不容易混淆。
6.3 迭代策略:从“修补”转向“补契约”
当生成代码出错时,多数人的做法是把报错贴回去让AI改。在契约先行框架下,更有效的做法是判断错误属于哪一层契约缺失,补契约而不是补代码:
本文评述:“补契约”而非“补代码”,是契约先行方法论在迭代环节的体现。补代码只解决当前样本,补契约解决同类问题。前者是打补丁,后者是修根因。长期看,契约会沉淀为团队资产,而补丁只会越积越多。
6.4 关于提示词长度与信息密度的权衡
有读者会担心:契约写这么长,会不会超出上下文窗口或稀释模型注意力?实践中的观察是,结构化契约比等长的自然语言描述更“省token”也更“抗稀释”。YAML/表格的每个字段都承载独立约束,模型对结构化输入的遵循度通常高于散文式描述。真正需要警惕的是“无关信息”——与加载无关的业务背景、历史沿革,这些才是注意力稀释的来源。关于长上下文下的信息利用,可参考Anthropic的上下文工程实践文章(anthropic.com/engineering)。
七、可验证交付:让AI生成代码自带“自检”
契约先行的最后一环,是让生成的代码能够“自证合规”。这要求提示词中明确交付要求:代码不仅要能跑,还要能报告自己是否遵守了契约。
7.1 三类必备自检
第一类:契约覆盖率断言。加载完成后,断言所有required列都已映射、所有列类型符合声明。失败时抛出包含具体列名和期望/实际值的异常。
第二类:数据画像日志。记录使用编码、行数、各列空值率、类型分布、异常值样本。这些日志是事后排查的依据,也是数据质量监控的输入。
第三类:可复现的失败信息。异常信息里应包含文件名、行号、原始字节(hex)、原始列名,让问题可以被精确定位,而不是“某处解码失败”。
class DataContractError(Exception):
def __init__(self, layer, detail, sample=None):
self.layer, self.detail, self.sample = layer, detail, sample
super().__init__(f"[{layer}] {detail} | sample={sample}")
def assert_contract(df, contract):
missing = [c for c in contract["required"] if c not in df.columns]
if missing:
raise DataContractError("L2-columns", f"缺失列{missing}", list(df.columns)[:5])
for col, spec in contract["types"].items():
if col in df and str(df[col].dtype) != spec:
raise DataContractError("L3-types", f"{col}期望{spec}实际{df[col].dtype}",
df[col].head(3).tolist())
7.2 用“测试先行”反向约束生成
一个进阶技巧是:在提示词里直接给出测试用例,让AI生成“能通过测试”的代码。测试用例本身就是契约的可执行表达。例如:
def test_gbk_file(tmp_path):
p = tmp_path / "a.csv"
p.write_bytes("销售日期,金额\n2024-03-05,\"1,234.5\"\n".encode("gbk"))
df = load_sales(str(p))
assert str(df["sale_date"].dtype).startswith("datetime")
assert df["amount"].iloc[0] == 1234.5
def test_missing_column_raises(tmp_path):
p = tmp_path / "b.csv"
p.write_text("日期,金额\n2024-03-05,1\n", encoding="utf-8")
with pytest.raises(DataContractError):
load_sales(str(p))
本文评述:测试用例是契约的“可执行切片”。把测试放进提示词,等于把“验收标准”提前交给AI。这比事后review代码更高效,因为它把正确性定义前置了。pytest的fixture机制(docs.pytest.org)非常适合构造这类临时文件测试。
八、工程落地:从单文件脚本到数据接入流水线
单文件脚本解决“这一次”,工程化解决“每一次”。把契约先行落到流水线,需要解决三个问题:契约存哪、谁来执行、如何演进。
8.1 契约的存放与加载
建议把契约作为独立YAML文件存放在数据接入仓库的contracts/目录,按数据源命名,纳入Git管理。加载器在运行时读取契约,而不是把规则写死在代码里。这样AI生成的代码可以保持“通用加载器”形态,具体规则由契约注入——代码与规则解耦,规则变更不需要重新生成代码。
8.2 分层职责
8.3 契约演进:把“意外”变成“规则”
流水线运行中总会遇到新情况:上游新增了一个列名别名、某批文件突然变成GB18030、日期格式多了一种。处理原则是把每次“意外”沉淀为契约变更,而不是临时改代码。具体流程:异常触发→人工确认→更新YAML→补充测试用例→回归验证。这样契约会随业务演进不断变厚,而代码保持稳定。
本文评述:契约的演进过程,本质上是把“隐性知识”显性化的过程。老员工知道“这个系统的金额列有时带人民币符号”,这个知识原本只存在人脑里;写进契约后,它变成了团队资产,也变成了AI可以遵循的规则。这是契约先行在组织层面的额外收益。
8.4 与现有工具链的衔接
契约不必从零造轮子。pandera可以直接把DataFrameSchema作为运行时校验;Great Expectations提供了更丰富的数据质量断言与报告;dbt的model contracts适合在数仓层做schema约束。选择哪种取决于团队技术栈,但核心思想一致:用声明式规格替代命令式修补。Great Expectations文档见docs.greatexpectations.io。
九、前沿预判与开放问题
把视角拉远,编码与列名问题会不会随着技术进步而消失?笔者认为短期内不会,但形态会变。
9.1 编码问题的长期性
GB18030-2022的强制实施会推动新系统统一到国标,但存量系统的生命周期往往以十年计。金融、政务、医疗等行业的遗留系统迁移成本极高,混合编码将在很长一段时间内持续存在。与此同时,多语言数据(中、日、韩、阿拉伯语)的接入需求上升,编码问题从“中文问题”扩展为“多脚本问题”,复杂度不降反升。
9.2 LLM在数据接入中的角色演进
当前LLM主要扮演“代码生成器”。笔者认为,更值得期待的方向是“契约生成器”:给定一批样本文件,让模型自动推断编码分布、列名别名、类型模式,生成候选契约,再由人审核确认。这把人的工作从“写规则”降为“审规则”,效率提升更显著。已有研究探索用LLM做schema匹配与数据剖析,例如在数据发现与集成任务中的应用,相关综述可参考VLDB/SIGMOD近年的datasets与benchmark工作。
本文评述:让AI生成契约比让AI生成代码更有杠杆效应。代码是一次性的,契约是可复用的;代码的正确性依赖契约,契约的正确性依赖人。把AI放在“提建议”的位置、人放在“做决策”的位置,是当前技术成熟度下更稳妥的分工。
9.3 开放问题
仍有几个问题没有好答案:其一,行级混合编码的自动检测在超大规模文件上的性能开销如何控制;其二,列名语义映射能否借助领域本体实现半自动化;其三,契约的“完备性”如何度量——我们怎么知道契约已经覆盖了所有坑?这些问题值得在工程实践中持续探索。
十、结论与操作清单
回到最初的问题:怎么提示AI才能生成可用的数据加载代码?答案不是某句神奇的提示词,而是一套方法:先把编码、列名、类型、失败模式写成可校验的契约,再让AI在契约边界内生成代码,最后用断言和测试验证交付。这就是“契约先行”的全部要义。
给读者的操作清单:
- 先抽样看字节:用
xxd或Python读前几百字节,确认编码与BOM。 - 写L1编码契约:声明回退链,gb18030优先于gbk,utf-8-sig优先于utf-8。
- 写L2列名契约:别名列表+清洗规则+覆盖率断言,映射表独立版本化。
- 写L3类型契约:显式dtype、千分位、日期格式、空值语义。
- 用五段式模板组织提示词,事实与契约分离,附反例约束。
- 要求代码自带三类自检:覆盖率断言、画像日志、可复现失败信息。
- 把测试用例放进提示词,让“验收标准”前置。
- 出错时补契约而非补代码,把意外沉淀为规则。
拓展阅读方面,除文中已给链接外,推荐关注Python官方codecs文档(docs.python.org/3/library/codecs.html)理解编解码器注册机制,以及Unicode官方FAQ(unicode.org/faq)澄清常见误解。视频方面,PyCon与PyData历年有大量pandas IO与数据质量主题的分享,可在其官方YouTube频道检索。
🔒 复制本站文章内容需登录并达到 L3。当前:未登录
微信扫一扫分享
打开微信「扫一扫」,扫描二维码后在微信中分享给好友或朋友圈。
💬 评论 (0)
评论功能已关闭

