
大家好我是 SiKi老师。游戏配置表里有的字段表示“没填”有的表示“填了但内容为空”。工具用标准库 csv 导出后再读回来这两种情况却分不开了。先别改业务逻辑我会先打印写出的文本和读回的类型再决定要不要引入明确的缺失标记。本文在 Linux 上的 Python 3.12.15 中使用标准库 csv 与 io另用 3.13.5 复跑同一脚本。样例全部自拟只在内存中往返不读写真实配置表也没有测试 Unity、Godot 的导入。下面讨论的是工具端的 CSV 往返约定不是引擎统一规定。官方在线文档标注的小版本是 3.12.15与本机 3.12.15 一致3.13.5 仅用于对照阅读器行为。一 默认写入会把 None 写成空字段准备一行同时包含缺失、空字符串、普通文本和整数importcsvimportiodefdump_row(row,**kwargs):bufio.StringIO()csv.writer(buf,**kwargs).writerow(row)returnbuf.getvalue()defload_row(text,**kwargs):returnnext(csv.reader(io.StringIO(text),**kwargs))sample[None,,forest,3]textdump_row(sample)backload_row(text)print(repr(text))print(back)print([type(c).__name__forcinback])本地输出是,,forest,3\r\n、[, , forest, 3]和[str, str, str, str]。None与写出后都变成空字段读回来都是空字符串原来的None再也回不来。整数3也会先变成文本3这是 CSV 读写的常见结果不是这次问题的重点。官方文档写得很直接为了方便对接数据库接口写入时会把None写成空字符串这不是可逆变换。默认阅读器也不做类型还原每个字段都是字符串。所以“导出后再导入还能区分缺失和空值”并不是默认方言能保证的。二 单独字段、假值和其他文本也不要混着猜再看几种容易误判的单字段写出结果forvaluein(None,,False,0,None,null):textdump_row([value])print(repr(value),,repr(text),load_row(text))本地里None和都写成\r\n读回都是[]。False写成False0写成0字符串None、null则原样保留。它们看起来像“空”或“无”但写出的文本并不相同也不能互相替代。我会把这些样例放进回归表而不是在排错时临时用if not cell一刀切。空字符串为假非空的False、0、None为真若再和业务里的“未配置”混用日志会变得很难读。本文没有把这些文本自动还原成布尔或数字。三 3.12 的 QUOTE_NOTNULL写出能分读回仍可能合从 3.12 起标准库增加了QUOTE_NOTNULL和QUOTE_STRINGS。用前者写出时None仍是未加引号的空字段真正的空字符串会被加上引号textdump_row([None,,x],quotingcsv.QUOTE_NOTNULL)print(repr(text))print(load_row(text,quotingcsv.QUOTE_NOTNULL))本地 3.12.15 得到,,x\r\n读回却是[, , x]两个字段仍然都是空字符串。官方文档注明由于缺陷这两个常量在 3.12 中不影响 reader该问题在 3.13 修复。我用 3.13.5 复跑同一段读回得到[None, , x]写出侧才能和读回侧对齐。因此若团队仍停留在 3.12不能只改quoting就宣称“往返可区分”。要么升级到已修复阅读器的版本并做验收要么在业务层使用下一节的显式标记。本文没有验证 Excel、Google 表格或其他软件是否识别这种引号差异。四 用显式缺失标记做业务往返如果协议允许把“缺失”编码成一个不会与真实内容冲突的标记空字符串继续表示“有字段但为空”MISSING__MISSING__defencode_optional(value):ifvalueisNone:returnMISSINGiftype(value)isnotstr:raiseTypeError(type(value).__name__)returnvaluedefdecode_optional(cell):ifcellMISSING:returnNonereturncell optional[None,,forest]encoded[encode_optional(v)forvinoptional]textdump_row(encoded)restored[decode_optional(c)forcinload_row(text)]print(repr(text))print(restored)本地输出__MISSING__,,forest\r\n和[None, , forest]。往返后与原列表相等输入列表也没有被修改。标记字符串要写进协议并禁止业务数据使用同一个记号需要转义或换记号时应先改协议再改代码。DictWriter也有类似陷阱缺键时若restval写出空字段显式传入None时默认写入同样变成空字段。本地用id,note两列验证缺note与noteNone得到的文本都是空值行。要区分“键不存在”和“值是 None”同样需要协议而不是依赖默认写入。五 回归时对照文本、类型和协议本次独立验证覆盖默认写入合并、单字段混淆、False/0与缺失不同、QUOTE_NOTNULL写出差异、3.12 阅读器限制、3.13 对照、哨兵往返、DictWriter缺键与None以及输入未被修改。全过程只在内存中运行不涉及网络、数据库或引擎导入。接手配置导出工具时可以先准备一行同时含None和的样例做一次写出再读回打印repr和类型。第一处无法区分的位置比在关卡编辑器里猜“这个空是不是没配”更省时间。协议写清之后再决定是否升级到 3.13 的阅读器行为或继续使用显式标记。你遇到的是导出后缺失和空值分不开还是表格软件把引号吃掉了给出一行自拟样例和你用的 Python 小版本就能开始排查不需要上传真实配置表。依据Python 3.12 csv 官方文档中 writer 对None写成空字符串、以及QUOTE_NOTNULL/QUOTE_STRINGS含 3.12 reader 缺陷说明的描述。示例和表达原创不引用具体课程信息。