一、报名人数为什么会变成一百二十三

小叶准备组织一次课程读书交流活动,想让 AI 帮忙写一个小程序:整理报名信息,算出目前有多少人,再生成一段给组织同学看的说明。这件事看起来不需要复杂算法。可是,第一版程序告诉他,原来有十二人,又来了三人,现在共有一百二十三人。

问题出在一次加法上。程序拿到的其实是两段文字:"12" 和 "3"。把文字接在一起,恰好就是 "123"。计算机顺利完成了程序指定的操作,小叶想要的“把两批人数相加”却没有发生。

本章的报名信息、时间与人数均为虚构教学设定。活动有二十四个席位;上午九点有十二份有效报名,且都已确认参加。九点十分又收到三份有效报名,这三人尚未确认。为了把问题集中在数据表示上,假定这些报名互不重复,参与资格已经处理完毕。我们现在要分清的是十五份报名、十二人确认,以及这些数字怎样进入程序。

AI 能生成很长的代码,也能根据报错修改代码。但要判断改动有没有解决问题,小叶至少需要知道:程序此刻拿到的是什么东西,对它做了什么,结果还代表原来的事情吗?变量和数据类型正好帮助我们回答这三个问题。

值是程序实际处理的内容,例如人数十五、编号“003”、状态“待确认”。变量让我们用一个名字找到当前要处理的值。数据类型规定这种值具有哪些操作和行为:数值可以参与算术,文本可以拼接,真假值可以表达一个判断。名称相近、屏幕上看起来相似的两项内容,未必在程序里具有相同的类型。

我们用 Python 作为具体例子,因为可以用很短的代码看见结果。本章讨论的赋值、转换和真假规则以 Python 为准。理解它们以后,再看其他语言时就知道该比较哪些规则;不需要先把各门语言的类型系统学一遍。

代码中的 print(...) 用来显示括号里的结果,多项结果可以用逗号分隔。下面的短代码块各自可以单独运行;先看输入和输出的关系,就能跟上讲解。# 后面的同一行文字是给人看的注释,不会作为计算指令执行。

可复制模板
print("12" + "3")
print(12 + 3)

两行分别显示 123 和 15。区别很小,作用很大:引号使数字字符成为文本。对于人数,我们想把数量合并;对于活动编号,拼接字符可能正是需要的操作。错误因此不能只凭某个符号来判断,必须把程序操作放回它承担的任务。

二、变量给值一个名字,名字本身不会替我们理解工作

小叶把当前报名人数记作 registered_count,把席位数记作 capacity。英文名字可以帮助阅读代码,但计算机不会因为名字里有“人数”,就自动拒绝小数或负数。先把名字、值和我们赋予它的业务含义分开,后面的错误就容易看清。

可复制模板
registered_count = 12
capacity = 24
remaining_seats = capacity - registered_count
print(remaining_seats)

这里显示 12,表示按当前报名数量计算,还有十二个未被报名占用的席位。这个结果暂时不涉及最终出席率,也不表示未来还会来多少人。程序只是执行了已经写出的计算关系。

第一行的 12 直接写在代码中,这种写法称为字面值。registered_count 是名字。中间的 = 是赋值:先求出右边的结果,再让左边的名字指向这个结果所对应的对象。第三行右边是一个表达式,它组合已有的值与运算,算出新的值。

在 Python 中,可以把变量理解为一个绑定到对象的名字。“对象”在这里不神秘,就是程序中具有值和类型的那个东西。当前十二这个整数对象可以通过 registered_count 找到;变量名没有被刻在整数里面。同一个名字以后还能绑定到别的对象。Python 的执行模型用名字绑定来说明这一关系。

这和数学等式的读法不同。数学里写出两个量相等,往往是在描述一个关系;程序里的赋值则是某个时刻执行的一次操作。小叶不是向计算机宣布“剩余席位永远等于这两个变量的差”,而是要求它此刻算一次,把结果记下来。

名字也不是引号里的文字。registered_count 会使程序寻找这个名字当前绑定的对象;"registered_count" 是由这些字母组成的一段文本。打印前者会显示当前人数,打印后者会显示变量名的字母。若在一个新程序里从来没有给这个名字赋值就使用它,Python 会报告找不到名字的 NameError,不会主动去报名表寻找人数。

起名因此是给人建立联系的一项工作。registered_count 比 a 容易看出用途,confirmed_count 又能把确认人数与报名人数分开。名字之间相似不代表它们可以互换;同一个整数十二,也可能分别表示十二名报名者、十二分钟、十二份材料。代码能处理这些数值,含义仍需要编写者和使用者约定。

小叶让 AI 修程序时,如果只说“这个变量错了”,双方很难确定要改哪一层。说清“这里统计的是有效报名份数,不是已确认人数;二者开始时都是十二,后面可能分开”,就给出了可以检查的含义差别。

三、赋值发生在此刻,后面的变化需要新的操作

上午九点,十二名报名者都已确认。小叶用当前报名数初始化确认数。随后来了三份新报名,但还没有得到这三人的确认回复。

可复制模板
registered_count = 12
confirmed_count = registered_count
remaining_seats = 24 - registered_count

registered_count = registered_count + 3
print(registered_count, confirmed_count, remaining_seats)

remaining_seats = 24 - registered_count
print(remaining_seats)

第一次显示 15 12 12,第二次显示 9。三个数字不是在同一时刻全部重新计算过的:报名数已经更新,确认数仍是十二,第一次打印的剩余席位却还是刚才算出的十二。

先看 registered_count = registered_count + 3。右边使用更新前的十二,得到十五,然后左边的名字重新绑定到十五。它不是数学里那个无解的“一个数等于自身加三”;它记录的是先读取、再计算、最后保存本次结果的过程。

confirmed_count = registered_count 则在上午九点执行。那一刻,两个名字都能找到十二这个整数。后来重新给 registered_count 赋值,并不会让 confirmed_count 自动转向十五。当前这个结果正符合案例:新增报名尚未确认,不能因为报名数增加就虚增确认数。

但是 remaining_seats 的情况不同。我们希望它反映最新报名量,它却保存着先前的计算结果。问题不是赋值规则忽然失效,而是程序还没有再次执行那条计算。在更新报名数之后重新计算剩余席位,才得到九。

执行到哪里 报名数 确认数 已算出的剩余席位
最初三次赋值结束 12 12 12
增加三份报名后 15 12 12
重新计算剩余席位后 15 12 9

这张表也解释了一个常见现象:代码里的值可能都算得对,却不是按我们需要的时点算的。“剩余席位为十二”对早先时刻成立,对当前就过时了。检查自动化结果时,除了问这个公式对不对,还要问它使用的是哪一刻的输入。

整数对象本身不会被这次赋值改造成十五。Python 的整数和字符串属于不可变对象:已经存在的这类对象不能被原地改写成另一个值;想得到新值,需要运算或构造结果,再决定把哪个名字绑定过去。本章用的都是这些简单值。下一章讲列表时,会遇到可以修改内容的对象,到时两个名字指向同一对象会产生另一类联系。这里说明的是名字的绑定关系,赋值本身并不承诺独立复制对象的全部内容。

还有一类时点问题发生在报告文字里。小叶若先生成“目前有十二人报名”,再更新人数,早先生成的那段字符串也不会自己改成十五。要得到新报告,就在需要时重新生成。要让结果自动刷新,就需要安排重新计算的操作;代码里曾经执行过一次赋值,并不会自动建立这种持续更新。

四、整数与小数,先看要保存什么数量

报名人数适合用整数,Python 中的类型名称是 int。十二人、零人和负三都是合法整数值;其中负三能不能作为报名人数,是另一个问题。对于当前活动,人数不应为负,但程序语言不会替我们附加这个活动规则。

小叶还要记录每位同学预计阅读材料的时间。有人填写十二点五分钟,仍以分钟为单位保存时,就不能用整数完整保留这个数值。Python 常用的带小数数值类型是 float,通常称为浮点数。先把它看作能表示一定范围和精度的小数近似值,稍后我们再讨论为什么“近似”会影响比较。

可以用 type(...) 查看值的类型。type(12) 对应 int,type(12.0) 对应 float,type("12") 对应 str,也就是字符串。十二与十二点零在数值上相等,但表示它们的类型不同;显示出来没有小数点,也不一定就说明来源一直是整数。

类型会影响运算结果。Python 中,两个整数使用 / 相除,结果是浮点数。例如,十五名报名者分到四张桌子,15 / 4 得到 3.75。这个数可以表达平均每桌人数,却不能直接成为某一桌实际安排的人数。

现在换一个安排问题:每桌最多坐四人,要知道需要几桌。这里的四指每桌容量,不再是已经给定的桌数。三桌坐满以后,还剩三人,因而需要第四桌。整数商和余数能把坐满的桌子与余下的人分开表示:15 // 4 得到 3,15 % 4 得到 3。这里 // 是向下取整的除法,% 取得相应余数。我们只用正人数说明这次分组,不需要为了得到四桌先展开完整条件程序。

再比较十六人:16 // 4 是 4,16 % 4 是 0,此时不能再机械地多加一桌。相同运算符给了我们数量信息,如何由信息安排桌子,取决于余下的人有没有地方坐。后面讲条件判断时,会把这个决定写进程序;现在已经能看出,只取整桌数而忽略余下人数,会少安排位置。

取整数也是一种规则选择。int(3.75) 得到三,是截去小数部分;对于负数,int(-2.7) 得到负二,方向是向零截断。// 则向下取整,所以 -7 // 3 得到负三。当前报名分组没有负人数,这个小对照只是说明二者不是同一种操作,不能因正数例子碰巧相同就互相替换。Python 内置整数转换明确区分了数值转换及其规则。

整数和浮点数的选择,应随记录的数量和单位确定。要保留几份材料,就不该随意接受二点七份;要保留十二点五分钟,也不该为了让程序运行就把它截成十二。类型选择和转换应当服务于原来想记录的数量,而不是只看某段代码喜欢哪种输入。

五、字符串保留字符,数字外形不决定它的用途

小叶给一份报名记录分配了编号“003”。编号里都是数字,但它的用途是识别记录,不是参加人数加法。当前活动约定编号恰好由三位字符组成,“003”应当原样保存;把它变成整数三,前面的两个零就消失了。

字符串是一串按顺序保存的字符,Python 用 str 表示这种类型。字符可以是汉字、字母、数字、空格和标点。程序中常用单引号或双引号界定字符串,它们本身通常是写法的边界,不是字符串内容的一部分。"003" 和 '003' 表示相同的三个字符。

可复制模板
registration_id = "003"
print(registration_id)
print(int(registration_id))
print(str(int(registration_id)))

依次显示 003、3、3。最后又变回文本,也不会自动找回丢掉的两个零。整数三没有保存“原来是三位编号”这项信息;要按三位格式补零,必须另外知道那个格式。对于来源规则不明确的记录,事后统一补零甚至可能生成原本不存在的编号。

同样的字符,放在不同字段里可以需要不同处理。“003”若表示三人,转为整数三可能合适;它若是当前报名编号,就要保留原样。决定转换的依据是字段含义。把所有看起来像数字的文字都转成数值,容易得到一份很整齐、却失去了原始区别的数据。

字符串的 + 做拼接,* 配合整数可以重复字符。"3" * 2 是 "33",不是六。字符串之间也可以比较,但对于只含数字字符的这类文本,比较仍按字符顺序进行:"12" < "3" 是真,因为第一位字符“1”排在“3”前面。整数比较 12 < 3 则是假。因此,名单若按文本形式的人数排序,顺序可能与人数大小不同。

空格同样属于字符。"已确认" 与 "已确认 " 不相等,后者末尾多了一个空格。使用 text.strip() 可以得到去掉首尾空白后的新字符串:点号表示对这段文本调用一个已有操作,括号表示执行它。这个操作适合处理当前表单中无意义的首尾空白,但不能推广成“所有空白都应删掉”。备注中的段落和姓名中的合法间隔是否重要,需要按字段判断。

因为字符串不可变,单独调用 text.strip() 不会把原名字绑定的文本原地改写。要保留清理后的结果,可以写 cleaned_text = text.strip(),或者明确重新给原名字赋值。小叶可以先保留原始输入,再另存处理后的文本,这样发现误处理时还有比较依据。

本节的关键不是记住许多文本操作,而是看见字符串保存了哪些区别:前导零、首尾空白、字符次序都可能参与程序判断。文本在人的阅读中允许宽松理解,在代码中如何视为相同,需要明确的操作或规则。Python 的文本入门说明提供了字符串拼接与不可变性的进一步例子。

六、布尔值回答一个判断,文字“假”却未必是假

小叶收到一份报名的确认状态,想让程序判断是否计入已确认人数。对于“这份报名是否已确认”这个明确问题,可以用 布尔值 表示结果。Python 的布尔类型叫 bool,只有两个值:True 和 False,分别对应真与假;首字母大写是它们在 Python 中的写法。

可复制模板
status_text = "待确认"
is_confirmed = status_text == "已确认"
print(is_confirmed)
print(type(is_confirmed))

这里显示 False,类型是 bool。== 检查两边是否相等,比较产生一个真假结果;它和用于赋值的单个 = 有不同任务。整条赋值先完成右边的比较,再把结果绑定到 is_confirmed。

这一判断只回答是否等于“已确认”。如果原始状态是“已取消”,结果也是假。两个不同的状态可以对同一个问题给出相同答案,布尔值因此可以方便后面的选择,却不一定保留全部原始信息。小叶若还要分别联系待确认者与统计取消者,就应保留原状态,不能只留下真假结果。

另一个容易混淆的操作是 bool(...)。它不是理解一段话的意思,而是按 Python 的真假规则把对象转换为布尔值。对于本章这些常见值,规则如下。

交给 bool 的值 结果 它按什么判断
False False 本来就是布尔假
0 或 0.0 False 数值为零
"" False 字符串为空
"False" True 是非空字符串
"0" True 也是非空字符串
"待确认" True 仍然是非空字符串

空字符串写成相邻的两个引号,中间没有任何字符。" " 中间有一个空格,因此不为空,转换结果也是真。这个判断没有阅读字符表达的含义。Python 官方真假规则说明了零、空内容与其他对象如何参与真假判断。

假如一个导出工具把未确认写成文本 "False",小叶的程序再用 bool("False"),就会得到真。AI 即使把这条代码解释为“把是否确认转成布尔值”,也没有改变它实际按非空文本判断的行为。这次错误需要修的是文本状态与布尔含义之间的对应,不能只把变量重命名成“已确认”。

如果数据来源明确约定,只允许 "True" 与 "False" 两种文本,程序可以先检查输入确实属于这两个值,再按规定转成真假。若来源还可能写“待处理”、空文本或拼错的词,就不能把不等于文本 "True" 的所有输入,都记成已经核实的未确认。未知内容可能需要核对,与已有明确记录的未确认状态不同。本章先讲清应当保留什么,条件与错误处理的代码会在后续章节展开。

布尔值的用途也由问题决定。报名数小于二十四,说明按当前计数还有席位;它不自动说明这份报名已确认,更不说明场地已批准使用。程序中的真,首先是某一个表达式按规则求值的结果。要把它作为行动依据,就应明确这个表达式究竟检验了什么。

七、零、空文本与 None,分别保留不同的信息

活动又增加了一项可选信息:每位同学额外需要几份纸质补充材料,允许填写零到五份。小叶发现,部分同学填了零,部分没有填写,另有一份记录还没从表单读到。这三种情况都需要进入统计,但其中保存的信息不同。

零份表示已经得到一个具体数量,确实不需要额外材料。未填写表示这个字段里没有文字,是否按零处理要看表单是否事先说明了默认规则。还没读到数据则连对方是否填写都不知道。若统一改成零,统计会把信息不足变成“已经知道不需要”。

Python 提供特殊值 None,常用来表示当前没有一个可用的结果或没有提供某项值。它不是数值零,也不是空字符串。我们可以在这个案例中约定:extra_copies = None 表示所需份数尚未取得;extra_copies = 0 表示已知需要零份。

可复制模板
extra_copies = None
print(extra_copies is None)
print(extra_copies == 0)
print(extra_copies == "")
print(bool(extra_copies))

依次显示真、假、假、假。判断是否为 None 常用 is None;这里是在检查对象是不是这个特殊的无值标记。一般数量或文字是否相等,仍使用前面学过的 ==,不要把 is 换作所有比较的写法。

值得注意的是,None、零和空字符串通过 bool 都得到假,但这不表示它们原本相等。真假转换只保留了按当前规则得到的一个二分结果,原有差别可能被压掉。上一节的待确认与已取消也说明了相同的关系:若后续决定需要更细的信息,就不能过早只存真假。

None 自己也不会说明信息为什么缺失。没有填写、尚未读取、读取失败,都可以被某个程序用它表示;如果这些原因对应不同处理,就需要另存状态。比如未填写可以提醒同学补充,读取失败需要先修复数据获取。是否应该提醒同学,不能仅由“结果是 None”决定,还要看程序对这个值的约定。

现在单独看十二份材料需求记录:十人明确填零,两人还没提交这项数量信息。小叶可以说已知的额外需求合计为零,还可以说有两人的情况未确定;他不能据此宣称这十二人都不需要材料。后续统计程序可以先分开已知数量与缺失情况,再决定怎样联系或准备。这比用一个默认零让总数顺利出现,更忠实于目前掌握的信息。

对于其他数值计算工具,缺失值还可能有不同表示。本章不把每一种都展开,只先形成一个习惯:看到零、空白或某种缺失标记时,问清它在这份资料中表示什么,再决定能否参加当前计算。

八、转换改变表示,也可能丢失原来的区别

现在回到开篇的十二加三。我们已经知道,问题不在 Python 的加号失灵,而在人数以文本进入了程序。若确认两段文本都表示整数人数,可以分别转换,再计算。

可复制模板
old_count_text = "12"
new_count_text = "3"
old_count = int(old_count_text)
new_count = int(new_count_text)
total_count = old_count + new_count
print(total_count)

结果为十五。这里先分别把两项内容读成数量,再相加。若先拼接得到 "123",最后才写 int(...),就只会把一百二十三这段文本读成整数一百二十三。转换放在哪一步,决定了程序究竟是在合并数量,还是在合并字符以后解释一个新数。

文本转换也不总能成功。int("12") 可以得到十二,int(" 12 ") 也能接受首尾空白;int("12.5")、int("十二") 和 int("") 则不符合这里要求的整数文本形式,会触发 ValueError。这个错误名称表示输入值不能按当前转换要求处理,并不等于所有带汉字的信息都不能存储;它们作为字符串当然可以保存。

程序使用 input(...) 接收一行键盘输入时,得到的是字符串,即使用户敲入的全是数字。其他文件、表单或接口怎样提供数据,要看各自的读取方式,不能统一假定全是文本。发现类型问题时应查看真实输入,而不是只看数据原来在哪个工具里显示。内置函数文档说明了 input 的返回行为。

小叶看到 int("12.5") 报错以后,可能让 AI “无论怎样都转成整数”。AI 给出先 float 再 int 的办法,确实会把 "12.5" 变成十二。但如果这是人数,为什么原输入会有半个人,仍没有解释;若这是预计阅读分钟数,半分钟被删掉,又改变了原来的记录。报错消失只说明某条操作可以执行,不等于这项转换符合任务。

转换大致会遇到三种不同效果。第一种保留当前任务需要的信息,例如人数文本 "12" 变为整数十二。第二种压掉了部分信息,例如 "003" 变为三、十二点五变为十二;是否可接受要看被压掉的区别是否重要。第三种根本没有得到可用结果,例如不能识别的整数文本。三种情况不宜都写成“数据清洗成功”。

比较转换前后的表示,可以检查信息丢失。str(int("003")) 只剩 "3",无法自行还原编号。int(float("12.5")) 只剩十二,无法看出原来是十二点五还是十二点九。前一个例子回到了原先的文本类型,属于一次来回转换。即使这类往返后文本外观相同,也只说明这个输入上的结果,不能保证所有输入都不丢信息。

一个实用的处理顺序是:先保留原始输入,明确它表示哪项信息;按需要去掉确定无意义的格式;采用适合这个字段的转换;再检查得到的值是否落在业务允许范围。这不是要求每一行数据都手工检查,而是先把程序要执行的规则说明白。AI 可以据此写批量处理,但它需要的规则来自我们对资料和任务的理解。

九、浮点数、显示与精度,区分三件看起来相同的事

小叶测量程序的两段处理耗时,记录为零点一秒和零点二秒。为了理解程序里的小数,我们单独看这两个教学数值。把它们相加并显示,Python 常见环境下会出现 0.30000000000000004。

可复制模板
total_seconds = 0.1 + 0.2
display_text = f"{total_seconds:.2f}"
print(total_seconds)
print(total_seconds == 0.3)
print(display_text)
print(type(display_text))

输出依次是那个略长的小数、False、0.30,以及字符串类型。小叶会自然觉得零点一加零点二就是零点三;在十进制数学运算里确实如此。差别来自程序保存这两个浮点值的方式。

常见计算机的浮点数采用有限位的二进制表示。有些十进制小数在这种表示中不能精确写完,于是保存为附近可表示的数。就像十进制用有限小数写三分之一时需要截断或舍入,换一种进制以后,哪些数能有限写完也会改变。计算是在已经保存的近似值上继续进行的,最后还可能发生舍入,所以不能要求每一步都与纸上的十进制精确运算完全一致。

这不意味着浮点数毫无用处。以秒记录实际测量耗时时,测量本身往往也有误差。关键是计算误差相对于任务要求有多大,以及比较时是否采用了合适标准。小叶不需要因为这个末尾小差异就把耗时判为明显不一致,但也不能把所有不同的时间都说成“只是浮点误差”。Python 浮点说明详细讨论了表示近似与显示的区别。

f"{total_seconds:.2f}" 是把数值放进文本的格式化写法。前面的 f 表示可以在大括号中放表达式,.2f 要求按两位小数显示。它产生了字符串 "0.30",并没有把原来的 total_seconds 换成一个精确的十进制 0.3。格式化后,原变量仍是原来的浮点值。格式化字符串说明给出了这种显示控制的用法。

因此,屏幕显示一样不等于内部数值相等;屏幕显示不同,也不一定意味着差异超过任务可接受范围。现在比较零点一加零点二的计算结果与数值零点三。如果允许二者相差不超过百万分之一秒,可以直接比较差值的绝对值:abs(total_seconds - 0.3) <= 0.000001。这个判断得到真。abs 取绝对值,使差异往哪个方向发生都按大小比较。

这里的百万分之一秒是特意选定的教学容差,不是所有工程任务都应沿用的标准。数据量级变大、测量工具变换,或者当前值代表的是费用而非耗时,都应重新决定什么差异可以接受。标准库还提供 math.isclose 等近似比较工具;理解比较目的以后,再按用途选择它的参数,比把所有 == 一律替换掉更可靠。

如果某项记录要求按十进制金额规则精确保存到分,可以选择用整数表示分,或使用 Decimal 表达十进制数,并明确舍入规则。比如教学单价零点一元、三份材料,可以先把单价表示成 10 分,整数相乘得到 30 分;在展示时再变成零点三元。这是改变保存单位,不是用显示格式修复已经发生的数值处理。

Decimal 也需要合适输入。从文本 "0.1" 构造,能保留这个十进制数;从已经近似的浮点 0.1 构造,会保留那个浮点值的十进制展开,而不会自动恢复我们原本想写的精确小数。十进制运算遇到三分之一等数时也有精度和舍入问题。此处知道选择的作用和输入条件即可,具体规则可查 Python Decimal 文档。

十、类型相同,仍可能把不同的事情算在一起

小叶现在能把人数转成整数,把文字状态转成适当判断,程序不再轻易报类型错误。但一个更隐蔽的问题仍然存在:类型只规定一部分计算行为,不会自动说明现实单位、计数范围和信息时点。

假设小叶记录 duration = 30,组织同学以为这是三十分钟,AI 生成的另一段程序却把它作为三十秒。两边都使用整数,每一条算术指令也可能都能执行,同一个数所表示的时长却相差六十倍。把名字改为 duration_minutes,在交接处明确转换成秒,会让人更容易发现单位关系;但名字只是提示,仍要确认代码真的乘了六十。

数量也有类似问题。十五份有效报名和十二名已确认者都是整数,如果程序用前者生成“已确认十五人”,类型检查发现不了这项错误。按本章条件,新增三份报名尚未确认,最后一句报告必须保留这个区别。若报名表还有重复提交,记录条数和人数又会分开;当前主例已经排除了重复,因此这里是在说明条件改变后的另一种计数口径。

取值范围同样需要另外检查。int("-3") 能成功得到负三,它是合法的 Python 整数;作为本活动当前报名人数却不合理。额外纸质材料要求零到五份,整数九也不符合这个要求。转换回答“能否按整数读出”,范围检查回答“这个整数是否符合这项记录”,两步解决的问题不同。

不要因为 Python 会执行某些混合运算,就认定含义相同。布尔值在 Python 中与整数有联系,True + 1 会得到二;这可以用于某些计数写法,但不表示确认状态本身就是人数二。理解这种行为有助于阅读 AI 的代码。

当数据来自 AI 对自然语言的整理,类型与含义之间还多了一次解释过程。例如,原话是“我还没确定要不要额外材料”,整理结果若直接是整数零,后续程序可以正常汇总,信息却已经在提取阶段丢失。应保留未确定状态,或标出需要确认的字段。要求输出整数的格式,不能代替判断原话是否已经给出整数答案。

这时小叶向 AI 提供几个具有区别的输入就很有用:“明确零份”“还没确定”“需要三份”“编号003”。让 AI 说明每种输入该保存什么,再核对它实际生成的值和类型,能够把问题从笼统的“理解不对”变为具体的表示错误。重点是保留对后续决定有用的区别,不是让所有字段看起来整齐一致。

十一、把最初的小程序接起来,检查信息在哪一步改变

我们现在把报名数量处理接成一小段程序。这里直接写入教学输入,不读取真实报名表;它只演示已经确认格式的两项人数如何转成数量、更新并生成报告。新报名是否有效、是否重复的条件,仍沿用开篇已经交代的设定。

可复制模板
old_count_text = "12"
new_count_text = "3"
capacity = 24
confirmed_count = 12

registered_count = int(old_count_text) + int(new_count_text)
remaining_seats = capacity - registered_count

report_text = (
    f"有效报名{registered_count}人,已确认{confirmed_count}人,"
    f"按报名数计算剩余{remaining_seats}个席位。"
)
print(report_text)

显示的是“有效报名15人,已确认12人,按报名数计算剩余9个席位。”括号让一条较长的文本赋值分行书写;相邻的这两段格式化字符串会连在一起。确认数在这里保持十二,因为三份新增报名尚未确认,程序不应把缺少的回复编造出来。

这段过程经过了几种不同表示。最初是两段人数文本;转换后是可以相加的数量;算完后得到新的数量;最后为了给人阅读,又把数量放进报告文本。最后一步回到文本没有问题,因为它的用途是展示,而不再承担这次人数加法。

人数文本、数量计算与报告展示的不同阶段
图 C01-01|人数文本、数量计算与报告展示的不同阶段

图中顺序说明了为什么“最后转成整数”不能修复最初的拼接错误。若先把两个文本连成“123”,两批人数的边界已经不在这个字符串里了;单独读取它只会得到一百二十三。需要分别解释原来的两项输入,再执行我们真正想要的数量合并。

同样,报告文字保存的是生成那一刻的结果。新增报名或收到确认以后,需要更新相应数量,再重新生成报告。重新运行打印旧文字的指令不会自动重新统计人数。现在小叶已经能定位:应该检查更新数量的操作,还是检查生成报告的时点,而不是不断要求 AI “把输出改成最新”。

对于编号“003”,这条数量处理路线并不适用。它应在记录识别过程中保留字符串形态;对于尚未取得的材料份数,应保留未取得状态,再决定何时补充。数据处理可以共用程序工具,却未必共用同一种转换规则。

小叶可以先让 AI 对这几项小输入运行程序,展示转换前后的值与类型,并解释每一步保存或舍弃了哪些信息。然后他再用“十六人刚好坐满四桌”“材料明确为零”“未取得材料需求”等变化检查相应规则。这样的小例能验证这些局部行为,但还不足以证明整个报名系统正确;完整文件读取、多人记录组织、错误恢复和持续更新,需要后面的内容继续接上。

十二、学会用程序中的值,说明一个真实错误

小叶最终解决的不是一个抽象的“AI算术能力不足”问题。第一版拿文本做了拼接;修订后,人数分别转为整数再相加,得到十五。确认人数保持十二,剩余席位在报名更新后重算为九,报告在这些计算之后生成。每一项改动都对应开篇的一项信息或时点。

他也有理由保留某些看起来不够整齐的数据。编号“003”继续作为文本,因为字符格式参与识别;材料份数未取得时保留 None,因为还不能据此统计为零;状态文本保留在需要区分待确认和取消的地方,真假值则用来回答一个已经明确的问题。统一类型只有在用途相同、转换规则明确时才有帮助。

读 AI 生成的程序,可以沿着一条很具体的线索追问:这个名字现在找到什么值?这个值是什么类型?这一操作产生了什么新值?新值能否支持我准备作出的判断?这条线索会把变量、类型和现实含义连接起来,而不是分别背完三份定义。

例如,看到“人数已经转为整数”,可以继续检查来源是否真的给了人数,负值是否被误接受;看到“状态已经转为布尔值”,可以检查转换保留的是非空与空,还是已确认与未确认;看到“保留两位小数”,可以检查这是显示要求,还是一项明确的数值舍入决定。问题越具体,越容易用一组小输入确认修改是否有效。

这一章还没有让我们组织上百份报名记录。单个值的含义与变化先清楚,下一章才能用列表、字典把多人信息放到一起,而不把一批“类型正确、含义混乱”的值更快地处理一遍。你已经可以从几行代码开始,与 AI 一起解释和修复眼前的行为;后面的知识是在这个基础上扩大能处理的任务。

练习与参考分析

练习一:哪一个结果应该跟着变化

下面是独立的小例。活动最初有八份有效报名,morning_count 用来保存早上第一次统计的数量,seats_left 要用于稍后的最新安排。

可复制模板
current_count = 8
morning_count = current_count
seats_left = 20 - current_count
current_count = current_count + 4
print(current_count, morning_count, seats_left)

会显示什么?如果最新安排要使用当前剩余席位,应该改哪里?早上统计数是否也应一起改?

**参考分析:**显示 12 8 12。报名数在最后一次赋值中增加到十二;另外两个名字没有因此自动重新计算。要让 seats_left 表示当前剩余席位,需要在新增报名以后再次执行 seats_left = 20 - current_count,得到八。

morning_count 的用途是保存第一次统计数,保持八才符合要求。不能把所有“不跟着变化”的值都当作错误,应该先看它代表早先快照还是当前计算结果。也不需要修改八这个整数对象;重新计算会得到新的结果,名字是否重新绑定,由具体赋值决定。

练习二:转换顺序和编号还原

小叶收到两批人数文本 "04" 和 "6",以及一项报名编号 "004"。本题约定前两项分别表示四人、六人,编号必须保留原来的三位字符。比较“先拼接两项人数再转整数”与“分别转整数再相加”。若编号先转成整数再转回文本,还能否恢复原样?

**参考分析:**先拼接得到 "046",按默认十进制转成整数为四十六;分别解释人数再相加得到十。这两种做法都可以在本题输入上执行,但只有后者表达两批人数合计。

编号往返转换得到 "4",不能仅靠整数四恢复原来字符。知道三位编号规则时可以另外按规则补零,不过更直接的做法是从开始保留 "004"。三位规则来自题目约定,不是整数值带来的知识。如果输入可能有两位、三位和带字母的编号,就不能把本题补零办法直接当成通用恢复工具。

练习三:真假结果相同,信息是否相同

一份导出资料分别出现 False、"False"、""、" " 和 None。把它们交给 bool 会得到什么?已知导出资料有时会把“未确认”写成文本 "False",能否直接用真假转换决定是否计入确认人数?

**参考分析:**结果依次是假、真、假、真、假。前者原本是布尔假;两个非空字符串都为真;空字符串和 None 为假。转换依据并不是对英文单词或空格作语义理解。

因此,文本 "False" 不能直接通过 bool 来表达未确认。应先明确来源允许哪些写法,再根据已确认的对应关系处理,不能识别的输入保留为待核对。空文本、未取得值和明确的否定可能对应不同后续操作,即使真假转换都为假,也不应自动合并为已经核实的未确认状态。

练习四:程序有总数了,信息就齐了吗

五份记录中的额外材料需求依次为零份、两份、尚未取得、零份、一份。AI 建议把尚未取得统一换成零,这样就能输出总需求三份。你会如何描述目前的结果?要继续安排材料,还缺哪一项决定?

**参考分析:**四份已取得的需求合计三份,还有一份情况未知。可以报告“已知需求三份,一份未确定”,不能仅凭现有资料说最终只需要三份。把未知改成零使计算顺利,却增加了原记录没有提供的结论。

小叶需要决定怎样取得剩余信息,或者在确实无法及时取得时,按活动规则作有依据的准备。题目没有给出未知需求的具体数值,不能替它编一个数量来证明安排已经完成。如果表单事先明确未填写视为零,还要确认这里的缺失确实是未填写,而不是读取失败;规则适用的原因不同,处理也可能不同。

练习五:显示一样的两个耗时

两个计算结果分别为 0.301 秒与 0.304 秒。都按两位小数格式化后,显示什么?如果任务要求两项耗时差不超过 0.001 秒,仅比较格式化文字是否足够?

**参考分析:**两者均显示 0.30。但原数值相差约 0.003 秒,超过题设的 0.001 秒。仅比较显示文本会丢失当前判断需要的细节,应当比较原数值之差的大小和给定容差。

这里的差异明显大于末尾浮点表示误差,不能因为本章讲过近似就把它忽略。反过来,对前文零点一加零点二与零点三之间的微小差异,可以在明确的容差下判断足够接近。显示精度、计算精度与任务允许差异相互有关,但并不是同一项规则。

练习六:一次“修好类型”的报告

小叶让 AI 整理一句报名备注:“编号007,预计阅读12.5分钟,材料需求还没确定。”AI 输出整数编号七、整数耗时十二、整数材料数量零,并说三个字段类型已经统一,下一步可以自动计算。请指出各字段的问题,并说明应如何保存。

**参考分析:**编号用于识别,应按来源规则保存文本 "007",不能用整数统一格式来消除有用的前导零。耗时应保留十二点五以及分钟单位;若后续以秒工作,应明确换算为七百五十秒,而不是先截断成十二分钟再换算成七百二十秒。

材料需求尚未确定,应保留未确定状态,例如在已约定的字段中使用 None,并保留其原因;零会把未知变为明确不需要。三个字段用途不同,没有都用整数的理由。好的转换结果不只是能被计算机接收,还要让后面的识别、时间安排和材料准备继续使用原话真正提供的信息。