大家好,我是 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 写成空字段
准备一行同时包含缺失、空字符串、普通文本和整数:
import csv
import io
def dump_row(row, **kwargs):
buf = io.StringIO()
csv.writer(buf, **kwargs).writerow(row)
return buf.getvalue()
def load_row(text, **kwargs):
return next(csv.reader(io.StringIO(text), **kwargs))
sample = [None, "", "forest", 3]
text = dump_row(sample)
back = load_row(text)
print(repr(text))
print(back)
print([type(c).__name__ for c in back])
本地输出是 ',,forest,3\\r\\n'、['', '', 'forest', '3'] 和 ['str', 'str', 'str', 'str']。None 与 "" 写出后都变成空字段,读回来都是空字符串,原来的 None 再也回不来。整数 3 也会先变成文本 "3",这是 CSV 读写的常见结果,不是这次问题的重点。
官方文档写得很直接:为了方便对接数据库接口,写入时会把 None 写成空字符串;这不是可逆变换。默认阅读器也不做类型还原,每个字段都是字符串。所以“导出后再导入还能区分缺失和空值”,并不是默认方言能保证的。
二 单独字段、假值和其他文本也不要混着猜
再看几种容易误判的单字段写出结果:
for value in (None, "", False, 0, "None", "null"):
text = dump_row([value])
print(repr(value), "=>", repr(text), load_row(text))
本地里 None 和 "" 都写成 '""\\r\\n',读回都是 ['']。False 写成 False,0 写成 0,字符串 "None"、"null" 则原样保留。它们看起来像“空”或“无”,但写出的文本并不相同,也不能互相替代。
我会把这些样例放进回归表,而不是在排错时临时用 if not cell 一刀切。空字符串为假,非空的 "False"、"0"、"None" 为真;若再和业务里的“未配置”混用,日志会变得很难读。本文没有把这些文本自动还原成布尔或数字。
三 3.12 的 QUOTE_NOTNULL:写出能分,读回仍可能合
从 3.12 起,标准库增加了 QUOTE_NOTNULL 和 QUOTE_STRINGS。用前者写出时,None 仍是未加引号的空字段,真正的空字符串会被加上引号:
text = dump_row([None, "", "x"], quoting=csv.QUOTE_NOTNULL)
print(repr(text))
print(load_row(text, quoting=csv.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__"
def encode_optional(value):
if value is None:
return MISSING
if type(value) is not str:
raise TypeError(type(value).__name__)
return value
def decode_optional(cell):
if cell == MISSING:
return None
return cell
optional = [None, "", "forest"]
encoded = [encode_optional(v) for v in optional]
text = dump_row(encoded)
restored = [decode_optional(c) for c in load_row(text)]
print(repr(text))
print(restored)
本地输出 '__MISSING__,,forest\\r\\n' 和 [None, '', 'forest']。往返后与原列表相等,输入列表也没有被修改。标记字符串要写进协议,并禁止业务数据使用同一个记号;需要转义或换记号时,应先改协议再改代码。
DictWriter 也有类似陷阱:缺键时若 restval="",写出空字段;显式传入 None 时,默认写入同样变成空字段。本地用 id,note 两列验证,缺 note 与 note=None 得到的文本都是空值行。要区分“键不存在”和“值是 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 缺陷说明)的描述。示例和表达原创,不引用具体课程信息。
网硕互联帮助中心


评论前必须登录!
注册