ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

配置表问题:布尔写成「是/否」读进来全是真?新手先看类型

配置表问题:布尔写成「是/否」读进来全是真?新手先看类型

大家好,我是 SiKi老师。技能配置表里有一列「是否启用」,策划填的是「是」和「否」。用 Python 读进来以后,明明写了「否」的技能,if row['enabled']:还是走进了“启用”分支,看起来像是整列都变成了真。先别怀疑判断写反了,第一步要看的是:读进来的值到底是什么类型。

本文在 Linux 上的 Python 3.12.15 中运行,另用 3.13.5 复跑同一脚本,下面引用的输出两边一致。表格样例全部自拟,只在内存和临时目录中演示,不读写真实游戏配置,也没有在任何引擎里验证技能开关效果。官方在线文档页面标注的小版本是 3.12.16,与本机安装的 3.12.15 差一个补丁号,正文以本机实测为准。

之前我写过 JSON 里false和"false"的区别,这篇只讲 CSV 配置表里用中文「是/否」当开关的情况,重点是怎样写一个说得清楚的转换规则。

一 先把读进来的值和类型打出来

自拟一份四行的技能表。第 3 行「是否启用」留空,第 4 行在「否」前面多了一个半角空格、后面多了一个全角空格(从表格软件复制时常见):

importcsvimportio sample="id,name,enabled\n1,火球,是\n2,冰箭,否\n3,雷击,\n4,治疗, 否\u3000\n"rows=list(csv.DictReader(io.StringIO(sample)))forrinrows:print(r["id"],repr(r["enabled"]),type(r["enabled"]).__name__)

本地输出:

1 '是' str 2 '否' str 3 '' str 4 ' 否\u3000' str

四个值全是str。官方文档写得很直接:csv 读出来的每一行都是字符串列表,不会自动做类型转换。也就是说,表格里的「是/否」读进来只是两个汉字,Python 并不知道它们表示开关。

二 为什么「否」也是真

接着用最常见的写法判断:

print({r["id"]:bool(r["enabled"])forrinrows})print(bool("否"),bool("False"),bool("0"),bool(""))

本地输出:

{'1': True, '2': True, '3': False, '4': True} True True True False

原因是 Python 的真值规则:字符串只要不是空串就是真,bool()不会去理解字面意思。所以「否」「False」「0」这三个非空字符串都是真,只有第 3 行的空串是假。表里全是「是/否」时,读进来就会“全是真”。

再顺手排除两个常见误解:

  • "否" == False的结果是False,"是" == True也是False,字符串和布尔值比较不会自动转换。
  • int("否")会直接报ValueError: invalid literal for int() with base 10: '否',不能靠先转数字再转布尔。

三 写一个显式映射,认不出就报错

我的做法是:先strip()去掉首尾空白,再转小写,然后去两张“白名单”里查;两张表都查不到就报错,并带上行号和列名,方便回头改表。

TRUE_WORDS={"是","true","1","yes","y"}FALSE_WORDS={"否","false","0","no","n"}defparse_flag(text,*,row_id,column):word=text.strip().lower()ifwordinTRUE_WORDS:returnTrueifwordinFALSE_WORDS:returnFalseraiseValueError(f"第{row_id}行{column}列无法识别为布尔:{text!r}")parsed,errors={},[]forrinrows:ifr["enabled"].strip()=="":errors.append(f"第{r['id']}行 enabled 列为空")continueparsed[r["id"]]=parse_flag(r["enabled"],row_id=r["id"],column="enabled")print(parsed)print(errors)

本地输出:

{'1': True, '2': False, '4': False} ['第 3 行 enabled 列为空']

第 4 行虽然带了半角和全角空格,strip()后仍识别为「否」(本地实测"\u3000".isspace()为True,所以全角空格也会被去掉)。再试一个表里不该出现的词:parse_flag("开", row_id="5", column="enabled")会抛出第 5 行 enabled 列无法识别为布尔: '开'。

这里有两个需要你按项目协议决定的点:

  • 空值怎么算:本文把空值单独记成错误,不偷偷当成False。如果你们约定“留空=关闭”,可以改成返回默认值,但要写进表头说明。
  • 白名单收多宽:本文同时接受中文和英文写法,是为了兼容下面要讲的回写。团队只用「是/否」的话,就把集合收窄,宁可报错也不要猜。

四 回写再读回来,同样要过映射

工具有时会把解析后的结果再写回 CSV。csv.writer写布尔值时会把它转成字符串:

buf=io.StringIO()w=csv.writer(buf,lineterminator="\n")w.writerow(["id","enabled"])w.writerow(["2",False])print(repr(buf.getvalue()))back=next(csv.DictReader(io.StringIO(buf.getvalue())))print(bool(back["enabled"]),parse_flag(back["enabled"],row_id="2",column="enabled"))

本地得到'id,enabled\n2,False\n',再读回来:直接bool()是True,过映射才是False。所以“写回去的是 False,读回来又变真”也是同一个原因。上面白名单里放了false,再配合lower(),就能接住False这种写法。

落到真实文件时,我同样按官方建议用open(path, newline='', encoding='utf-8')交给 csv 模块。临时文件实测与内存一致:第 2 行读出来是'否',bool()仍为True,跑完临时目录里只有自拟的skills.csv。

五 一步步排查清单

遇到“开关怎么改都是开”,我会按这个顺序查:

  1. 打印repr(值)和type(值).__name__,确认是不是字符串。
  2. 看判断写法,是不是直接if 值:或bool(值)。
  3. 看单元格里有没有首尾空格、全角空格。
  4. 用显式映射转换,认不出的值报错并带行号,空值按协议单独处理。
  5. 把「是」「否」「空」「带空格的否」「不认识的词」「回写的 False」写进回归样例,改代码后先跑一遍。

本次独立验证脚本覆盖了上面每一条,在 3.12.15 和 3.13.5 上都通过。

你们的配置表开关列是怎么填的,「是/否」「1/0」,还是 TRUE/FALSE?可以贴一行自拟的样例和现在的判断写法,我们一起看映射该收多宽。

依据:Python 3.12 csv 模块(docs.python.org/3.12/library/csv.html)关于读取结果为字符串、不做自动类型转换的说明,以及内置类型文档(docs.python.org/3.12/library/stdtypes.html)中的真值测试规则。示例和表达原创,不引用具体课程信息。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进