把Python练习题答案变成可跑断言与自测题库
简介Python 练习题库文档面向正在学习 Python 基础语法、准备课程作业或入门笔试的读者内容围绕填空题与代码结果判断展开覆盖标准库 math、None 空类型、变量类型与内存地址、列表/元组/字符串有序序列、重复运算、map 与 zip、切片、列表推导式、字典 items、排序与 lambda、range、join、转义字符等高频知识点。文档以题目加参考答案的形式组织便于对照自测和查漏补缺。压缩包仅含 1 个 docx 文件大小约 165KB轻量易取适合打印或导入笔记软件后按章节复习。目前已有 1007 人学习说明其在 Python 基础练习场景中有一定参考价值。读者可从中获得一份可直接核对答案的练习清单并借由 30 余道填空题梳理表达式求值、切片边界、字典与列表推导式等易错点适合作为课前预习、课后巩固或面试前快速回顾的材料。1. Python 练习题答案文档一份能直接对着跑的结果清单打开一份标着「python练习题-答案.docx」的文件多数人第一反应是把它当题库刷。我的看法相反这类文档真正的价值是结果清单——每道题后面括号里那个值是可以拿去和解释器对账的。填空题里的sqrt、None、type()、id()阅读程序题里的a,b,c / a:b:c、13 16 15程序填空题里那几个空全都是可验证的确定性输出。它解决的不是「学不学 Python」的问题而是「我写的代码和标准答案差在哪一行」的问题。适合谁正在带入门课、要出卷子的人以及自学到函数默认参数、切片、字典合并这几块开始犯迷糊的人。下面我按填空、阅读、填空补全、编程四类题拆开讲顺带把这份文档里几处容易照着抄错的坑标出来。2. 填空题对账把 51 个结果跑成可回归的断言填空题占的篇幅最大从math.sqrt一路排到type(12*3.14)。这类题看着零散其实全部落在几个固定考点上内置函数、序列有序性、切片、推导式、字符串方法、排序的 key。我的做法是不背答案直接把括号里的值写成断言跑一遍就知道自己哪条理解偏了。2.1 内置函数与对象身份type、id 与不可变对象的地址变化文档第 4、5 两题问的是type()和id()第 8 题紧接着用id()设了个陷阱x 3id(x)返回某地址执行x 6之后判断id(x) 原地址答案是False。这条不是玄学整数不可变实际是重新绑定到一个新对象。第 9 题x * 6得18同一机制。我把这几条连同基础类型判断一起写成一个脚本import math # 填空题 1-9内置函数与对象身份 assert math.sqrt(16) 4.0 # sqrt 返回浮点不是整数 assert type(None).__name__ NoneType # None 是空类型常量 assert type(1 2 * 3.14) is float # 第 51 题混合运算向 float 提升 x 3 addr id(x) x 6 assert (id(x) addr) is False # 不可变对象重绑定地址会变 assert x 9 y 3 y * 6 assert y 18 # 文档第 9 题 # 小整数缓存会让低值 id 复用得很有迷惑性别用 id 判断相等 assert 256 is 256逻辑说明前四条把「返回类型」和「值」分开验sqrt返回4.0而不是4这是填空题最容易写错的一处。第 5 行用is float而不是 float因为type()返回的是类型对象用身份比较语义更准。参数上没什么可调的唯一要留意的是id()的返回值依赖解释器实现文档里那个496103280只在小整数缓存范围内有讨论意义换台机器、换个进程都会变所以断言只比对「是否相等」不写死具体数值。最后一行提醒自己id相同不代表业务上相等反过来is为真在某些缓存区间又会误导人做练习时知道这层就够了别拿它写生产判断。2.2 切片、推导式与 range下标边界怎么算第 11 题aList[3:7]得[6, 7, 9, 11]第 14 题末元素下标-1第 17 题list(range(1,10,3))得[1, 4, 7]第 18 题list(range(6))[::2]得[0, 2, 4]第 49 题x[100:200]得[]而不是报错。这五条放一起左闭右开、越界切片不抛异常这两条规则就立住了。第 33 题更有意思x[len(x):] [1, 2]用切片赋值在尾部追加得[3, 5, 7, 1, 2]。aList [3, 4, 5, 6, 7, 9, 11, 13, 15, 17] assert aList[3:7] [6, 7, 9, 11] # 左闭右开第 3 位取到第 6 位 assert aList[-1] 17 # 负索引取末元素 assert list(range(1, 10, 3)) [1, 4, 7] # 步长 3不含终点 10 assert list(range(6))[::2] [0, 2, 4] # 缺省起点终点只给步长 x list(range(20)) assert x[100:200] [] # 越界切片返回空列表不报错 y [3, 5, 7] y[len(y):] [1, 2] # 切片赋值等价于尾部扩展 assert y [3, 5, 7, 1, 2] # 推导式两条重复常量 / 条件过滤 assert [5 for i in range(10)] [5] * 10 assert [i for i in range(100) if i % 13 0] [0, 13, 26, 39, 52, 65, 78, 91]逻辑说明切片那段的关键参数是[start:stop:step]三个位置stop永远取不到step为负时方向反转第 33 题用的就是「start len且stop缺省」这种边界写法。推导式那两条对应文档第 12 题和第 30 题注意第 30 题的答案里包含0因为range(100)从 0 开始0 % 13 0成立不少人手写时漏掉这个 0。x[100:200]这条值得单独记切片的上下界会被静默裁剪到合法区间索引x[100]才会抛IndexError两者别混。2.3 字符串与排序max/min 的比较依据和 key 的作用第 23、24、25 题是一组连环套x [11, 2, 3]max(x)得3min(x)得11而max(x, keylen)又得11。三行代码三种结果原因只有一个——不加key时按字符串字典序逐字符比加了keylen才按长度比长度相同则保留先出现的那个。第 22 题sorted([111, 2, 33], keylambda x: -len(str(x)))得[111, 33, 2]也是同一套路负号把长度降序。第 50 题对字典.values()排序得[3, 9, 78]纯数值排序没歧义。x [11, 2, 3] assert max(x) 3 # 字典序3 2 11 assert min(x) 11 # 1 2 3逐字符比 assert max(x, keylen) 11 # 按长度比长度并列取先出现者 assert sorted([111, 2, 33], keylambda v: -len(str(v))) [111, 33, 2] # 字典合并后按键排序第 48 题 d {b: 1, a: 2} d.update({a: 3, d: 4}) assert sorted(d.items()) [(a, 3), (b, 1), (d, 4)] assert sorted({a: 9, b: 3, c: 78}.values()) [3, 9, 78] # 字符串方法几条find 大小写敏感、replace 全量替换、translate 逐字符映射 assert Hello world. I like Python..find(python) -1 assert abcab.replace(a, yy) yybcyyb table .maketrans(abcw, xyzc) assert Hellow world.translate(table) Helloc corld逻辑说明key参数是这一组的核心它接收一个单参函数排序或取极值时先对每个元素调用一次、用返回值比较原值本身不变。第 25 题max(x, keylen)在多个元素长度相同时返回第一个遇到的这条规则文档没明说但答案11只能这么解释。translate那条要看清映射表a→x、b→y、c→z、w→cHellow world里的w被换成c、o不在表里原样保留于是成了Helloc corld。第 43 题find返回-1是大小写不匹配不是语法错误这个区分在实际排错时很有用。2.4 字典、zip 与格式化把 51 条答案落成一个回归脚本剩下的题集中在字典构造、zip、join、format和%格式化上。第 13 题dict(zip(a, b))两列表转字典第 34 题list(zip([1,2],[3,4]))得[(1,3),(2,4)]第 40 题位置参数{1}在前{0}在后第 39 题%d,%c % (65, 65)得65,A。我一般把整份填空题的答案组织成一个可重复执行的断言文件跑通即为对账完成# 字典与 zip a [name, age, sex] b [Dong, 38, Male] assert dict(zip(a, b)) {name: Dong, age: 38, sex: Male} assert list(zip([1, 2], [3, 4])) [(1, 3), (2, 4)] # 格式化位置参数按索引取不按书写顺序 assert {1}.format(65, 97) 97 assert The first:{1}, the second is {0}.format(65, 97) The first:97, the second is 65 assert %d,%c % (65, 65) 65,A # %c 按码位转字符 # 字符串拼接与切分 assert .join(list(hello world!)) hello world! assert :.join(abcdefg.split(cd)) ab:efg assert ,.join(a b ccc\n\n\nddd .split()) a,b,ccc,ddd # print 的 sep 参数 import io, contextlib buf io.StringIO() with contextlib.redirect_stdout(buf): print(1, 2, 3, sep:) assert buf.getvalue().strip() 1:2:3逻辑说明%c那条参数是码位整数65对应A%d同一个65输出数字两个格式符吃同一组元组的不同位置逻辑要分清。最后一段用redirect_stdout把print的输出抓下来比对是验证「输出类」题目最省事的办法比对着屏幕肉眼核对可靠。split()不带参数时按任意空白切分并丢弃空串所以连续空格和换行都被吃掉这是第 44 题答案能那么干净的原因。整段脚本直接存成check_fill.py改一行跑一次比翻文档快得多。3. 阅读程序题用 trace 和断言复原 25 段输出阅读程序题是这份文档里信息密度最高的部分25 段代码输出结果有的写在题后有的只给了功能描述。照着看很容易「看懂了但写不对」尤其涉及默认参数、可变默认值、循环里的else、break与print交错的时候。我一般把每段代码原样抄进函数把预期输出写成断言跑挂了再回头读逻辑。3.1 默认参数与可变对象old_list[]那条必须亲手跑一次第 20 题是这份文档里最值得单独拎出来的一段def demo(newitem, old_list[])先demo(a)再demo(b)输出[a]和[a, b]。原因是默认值在函数定义时求值一次[]这个列表对象被所有调用共享。这条光看答案记不住必须自己敲一遍才会在以后写代码时条件反射地避开。第 3 题Sum(a8, c2)得13是因为b取默认值3关键字传参不改变位置对应关系。def demo(newitem, old_list[]): # 默认值只在定义时创建一次 old_list.append(newitem) return old_list assert demo(a) [a] assert demo(b) [a, b] # 上一次的修改被保留了 def fixed(newitem, old_listNone): # 正确写法用 None 做哨兵 if old_list is None: old_list [] old_list.append(newitem) return old_list assert fixed(a) [a] assert fixed(b) [b] # 每次都是新列表 def Sum(a, b3, c5): return sum([a, b, c]) assert Sum(a8, c2) 13 # b 走默认 3 assert Sum(8) 16 # b3, c5 assert Sum(8, 2) 15 # b2, c5逻辑说明demo与fixed是同一功能的两版写法对比着跑一遍就知道为什么默认值不能写可变对象。Sum那三条覆盖了「全关键字」「只给位置」「位置加缺省」三种调用形态参数绑定顺序是先按位置填、再按关键字补、剩下的取默认冲突时报TypeError。第 23 题f(a3)会报错因为形参里根本没有a这条建议单独跑一次看异常信息长什么样比记结论有用。3.2 循环 else、break 与自增顺序第 4 题那 4 行输出怎么来的第 4 题那段代码排版在文档里是乱的i1开头while i1:里面if i4则print后i1再break否则print后i1循环体末尾还有一个i1。手推一遍i1打印 1i变 2i2打印 2i变 3依次到i5时进if打印 5i变 6 然后break。所以输出是1到5各一行。第 6 题for带else循环正常结束才执行else输出1第 7 题在i 50时breakelse不执行只打印1275。import io, contextlib def capture(fn): buf io.StringIO() with contextlib.redirect_stdout(buf): fn() return buf.getvalue().split() def q4(): i 1 while i 1: # i 非 -1 即真这里的条件写法很怪但不影响 if i 4: print(%d % i) i 1 break print(%d % i) i 1 i 1 # 每次正常循环 i 前进 2 assert capture(q4) [1, 3, 5] # 严格按代码走是 1 3 5 不是 1 2 3 4 5 def q7(): s 0 for i in range(1, 101): s i if i 50: print(s) break # break 跳过 else else: print(1) assert capture(q7) [1275] # 1 到 50 求和逻辑说明这段是我建议优先动手验证的一处因为文档里第 4 题的答案缺失、代码缩进在转换过程中也可能变形靠肉眼推容易得出1 2 3 4 5。按「正常分支里i 1写了两次」理解结果应是1 3 5如果只有一次才是1 2 3 4 5。两种理解都建议跑一遍看哪一种和原文档代码一致——这也是读这类 docx 的通病缩进在复制粘贴时会丢层级消息capture这种做法能把差异摊开。else子句的规则也要顺带记住for/while的else只在循环未被break中断时执行q7就是反例。1275是1 2 ... 50和第 6 题「正常结束打印 1」形成对照。3.3 拆包、可变长参数与递归小函数第 21、22、25 题第 21 题func5(a, b, *c)打1 2再打length of c is 4, c is (3, 4, 5, 6)*c收走多余位置参数成元组。第 22 题demo(*para)算出均值2.5筛出大于均值的3, 4拼成(2.5, 3, 4)。第 25 题demo(20, 30)先交换保证m n再用循环算最大公约数返回(10, 60)第二个值是乘积除以公约数正好是最小公倍数。def func5(a, b, *c): return a, b, len(c), c assert func5(1, 2, 3, 4, 5, 6) (1, 2, 4, (3, 4, 5, 6)) def demo(*para): avg sum(para) / len(para) g [i for i in para if i avg] return (avg,) tuple(g) assert demo(1, 2, 3, 4) (2.5, 3, 4) # 单元素元组必须带逗号 def gcd_lcm(m, n): if m n: m, n n, m p m * n while m ! 0: r n % m n m m r return n, p // n assert gcd_lcm(20, 30) (10, 60)逻辑说明(avg,) tuple(g)里的逗号是关键(avg)只是加括号的浮点数(avg,)才是单元素元组第 22 题答案能写成(2.5, 3, 4)靠的就是这个拼接。gcd_lcm用的是辗转相除r n % m之后把n挪到m、r挪到n终止条件m ! 0返回时n已是最大公约数p // n取整除得最小公倍数。第 24 题的sort函数返回元组供拆包n1, n2 sort(3, 2)得n1 is 2、n2 is 3这类「返回多个值」的写法在这份文档里反复出现是它想强化的一个点。4. 程序填空题与编程题10 个空和 16 道题的补全顺序填空题给的是骨架编程题给的是需求两者其实是同一类任务的两端前者要你对着上下文猜实参后者要你从零组织代码。这一章按「先补空、再成段」的顺序推进每一步都给出可跑的版本。4.1 十个空逐个补随机频次、金字塔、质因数与回文第 1 题三个空填randint、50、0r.get(i, 0) 1是字典累计频次的标准写法。第 2 题填string、random.choice(x)、keylambda v: v[1]、%-15s%5d左对齐 15 位、右对齐 5 位。第 3 题金字塔填n int(n)、 * (n - i)、end其中end是为了让内层print(, end)不换行。第 4 题质因数分解缺的三处是开头print(x, , end )、条件x % k 0、不整除时k 1。import random, string # 空 1随机频次统计 x [random.randint(1, 20) for _ in range(50)] r {} for i in x: r[i] r.get(i, 0) 1 assert sum(r.values()) 50 # 空 2高频字符左对齐 15 右对齐 5 pool string.ascii_letters string.digits string.punctuation y [random.choice(pool) for _ in range(1000)] d {} for ch in y: d[ch] d.get(ch, 0) 1 items list(d.items()) items.sort(keylambda v: v[1], reverseTrue) line %-15s%5d % items[0] assert len(line) 20 # 空 4质因数分解 def factorize(x): print(x, , end ) k 2 while x 1: if x % k 0: print(k, end ) x // k if x 1: print(*, end ) else: k 1 factorize(60) # 60 2 * 2 * 3 * 5逻辑说明r.get(i, 0) 1的第二个参数是缺省值键不存在时返回 0避免KeyError这是频次统计的固定套路。items.sort(keylambda v: v[1])取元组第二位即次数排序reverseTrue降序。格式化串%-15s%5d里-表示左对齐、5表示总宽 5两者拼起来正好 20 个字符这条可以用len断言。factorize里x // k是整除赋值不从x里除掉因子就会死循环是这道题最容易翻车的位置。第 8 题回文填空的四个空是len(s) - 1、s[low] ! s[high]、break、high - 1双指针从两端向中间夹不相等就提前退出。4.2 编程题落地文件写、切片排序、素数判断与成绩统计第 1 题写文件open(rD:\test.txt, a)用原始字符串避免转义print(..., filefp)写入。第 2 题偶数下标降序x[::2]取出偶数位、排序后x[::2] y切片赋值回填这个「步长切片赋值」是本题的关键。第 3 题素数判断range(2, int(math.sqrt(v)) 1)注意1保证边界被覆盖for-else结构让「未找到因子」自然落到else。import random, math from pathlib import Path # 第 1 题写成可移植路径不要硬编码盘符 p Path(test.txt) p.write_text(hello world, encodingutf-8) assert p.read_text(encodingutf-8) hello world # 第 2 题偶数下标降序 x [random.randint(0, 100) for _ in range(20)] even_part x[::2] even_part.sort(reverseTrue) x[::2] even_part assert all(x[i] x[i 2] for i in range(0, 18, 2)) # 第 3 题素数判断 def is_prime(v): if v 2: return No for i in range(2, int(math.sqrt(v)) 1): if v % i 0: return No return Yes assert is_prime(77) No assert is_prime(97) Yes # 第 5 题前 10 升序后 10 降序 data [random.randint(0, 100) for _ in range(20)] head data[:10]; head.sort() tail data[10:]; tail.sort(reverseTrue) assert head sorted(data[:10]) and tail sorted(data[10:], reverseTrue)逻辑说明第 1 题我把D:\test.txt换成相对路径原始文档这么写是为了让初学者看清盘符写法但直接跑到其他系统上必挂实际练习时用pathlib更省事。第 2 题的断言用相邻比较偶数位上每个元素不小于其后第二个偶数位元素等价于「降序」比复制一份sorted去比对更能体现排序本身的意图。is_prime里v 2单独处理因为1不是素数、0和负数更不是for正常走完就落到最后一行返回Yes不必写else。第 8 题的成绩统计用max(scores.values())、min(...)、sum(...) / len(...)三件套再用列表推导筛出最高分同学的姓名注意score highest的判断放在推导式的if里。4.3 字符串与流程控制凯撒密码、闰年与分段函数第 13 题凯撒密码是三字符平移 3 位核心是chr(ord(a) (ord(p) - ord(a) 3) % 26)减基址、加偏移、取模、再加回基址四步不能少任何一步否则小写字母会溢出到其他字符区间。第 6 题闰年判断x % 400 0 or (x % 4 0 and x % 100 ! 0)。第 7 题分段函数四个区间x 0 or x 20返回 00 x 5返回x5 x 10返回3x - 510 x 20返回0.5x - 2。def caesar(s, shift3): out [] for p in s: if a p z: out.append(chr(ord(a) (ord(p) - ord(a) shift) % 26)) elif A p Z: out.append(chr(ord(A) (ord(p) - ord(A) shift) % 26)) else: out.append(p) # 非字母原样保留 return .join(out) assert caesar(ABCXYZ) DEFABC assert caesar(Hello, World!) Khoor, Zruog! def piecewise(x): if x 0 or x 20: return 0 if x 5: return x if x 10: return 3 * x - 5 return 0.5 * x - 2 assert piecewise(-1) 0 assert piecewise(3) 3 assert piecewise(7) 16 assert piecewise(12) 4.0逻辑说明caesar里shift做成参数而不是写死 3是为了验证时方便改成 0 看是否恒等、改成 13 看是否对称% 26保证绕回。(ord(p) - ord(a) shift) % 26这个括号顺序必须严格先减基址得到 0-25 的序号加偏移取模再加基址还原码位少一层括号就会出现z平移后跳到标点的情况。piecewise用连续if而不是elif链因为每个分支都return了后面的条件天然不会被执行读起来更短。第 9 题的补考名单统计用while循环反复询问输入strip()去空格、split()拆姓名和成绩注意成绩要转float再比较字符串直接比100 60会出真值。5. 避坑排查这份答案文档的六处典型翻车5.1 翻车点一照着id()答案记「地址会变」却用它判断相等现象有人看到第 8 题答案False就以为「变量一改id必变」写业务代码时拿id(a) id(b)判断两个值是否相等。原因id()返回的是对象身份小整数、短字符串在解释器里有缓存复用a 3和b 3的id很可能相同反过来两个内容相等的列表id必然不同。解决判等用判同一对象用isid()只用于理解可变与不可变的区别不进生产判断。5.2 翻车点二docx复制出来的代码丢了缩进层级现象把阅读程序题贴进编辑器运行报IndentationError或输出对不上。原因文档转文本时制表符和空格混用for循环体、if分支的层级被压平第 4 题、第 16 题这类嵌套较深的段落最容易出问题。解决粘贴后先开编辑器的「显示空白字符」核对层级或者按语义重排一遍再跑跑出来的结果和你手推的不一致时优先怀疑缩进而不是逻辑。5.3 翻车点三默认值[]与单元素元组(x,)两处语法陷阱现象函数连续调用返回的列表越来越长return (avg)返回的是浮点数不是元组拆包时报错。原因可变默认值在定义时创建一次被共享括号不构成元组逗号才是。解决默认值一律用None加哨兵判断单元素元组必须写(avg,)。第 20 题和第 22 题正好踩这两个坑建议对照跑一遍。5.4 翻车点四translate映射表大小写与max的key忘了给现象第 47 题结果对不上第 23 到 25 题三个答案混成一个。原因maketrans(abcw, xyzc)只映射小写原文Hellow world里的H不在表内保持原样max(x)不加key走字典序加了keylen才走长度长度并列时取先出现的元素。解决验证时把映射表打印出来逐字符对用key的函数建议单独测一次它的返回值比如[len(v) for v in x]看清比较依据再套max。5.5 翻车点五切片越界不报错索引越界才报错现象以为x[100:200]会抛异常写防御代码时多加了try或者反过来写成x[100]崩在线上。原因切片会把上下界静默裁剪到[0, len]区间返回合法片段或空列表单下标访问才做严格检查。解决需要「取不到就空」的语义用切片需要「取不到就报错」的语义用下标两者不要互相替代。第 49 题就是这条规则的现成样本。5.6 翻车点六print的end与sep混用导致输出格式全错现象金字塔、九九乘法表、高频字符统计三题输出挤成一行或每次多一个空行。原因print默认end\n内层循环忘了传end就会每打一个字符换一行sep只在一次print多个参数时生效和end是两回事。解决控制换行改end控制参数间分隔符改sep格式化宽度交给%或format三者分工别混。第 2、3、6 题都涉及这组参数跑之前先想清楚要调哪一个。6. 把答案文档变成自测题库一个 40 行的判分脚本刷完这四类题最容易出现的问题是「当时会了一周后忘光」。我自己的习惯是把答案文档里的确定性结果全部抽成用例写成一个不依赖第三方库的判分脚本每次改完代码跑一遍红了就是退化了。这套做法对两类人特别值带课的可以拿它批量检查学生提交自学的可以拿它当阶段性体检。核心思路是把题号、题干摘要、期望值三列存成结构化数据执行体用eval或函数调用比对结果后统一输出报告。import io, contextlib, traceback CASES [ (填空11, aList[3:7], lambda: [3,4,5,6,7,9,11,13,15,17][3:7], [6,7,9,11]), (填空25, max(x, keylen), lambda: max([11,2,3], keylen), 11), (填空33, 切片尾部追加, lambda: (lambda v: (v.__setitem__(slice(len(v), None), [1,2]), v)[1])([3,5,7]), [3,5,7,1,2]), (填空49, x[100:200], lambda: list(range(20))[100:200], []), (阅读20, 可变默认值, lambda: (lambda f: (f(a), f(b)))(lambda i, o[]: (o.append(i), o)[1]), ([a], [a,b])), ] def run(cases): passed failed 0 for name, desc, fn, expect in cases: try: got fn() ok got expect except Exception: got, ok traceback.format_exc(limit1), False if ok: passed 1 print(f[PASS] {name} {desc}) else: failed 1 print(f[FAIL] {name} {desc}\n 期望 {expect!r}\n 实际 {got!r}) print(f\n合计 {passed failed}通过 {passed}失败 {failed}) return failed 0参数说明与用法CASES每项四元组依次是题号、题干摘要、无参可调用对象、期望值。run里用try包住执行体任何异常都记为失败并打印一行堆栈避免一条挂掉整批中断。返回布尔值方便挂到命令行的退出码上写python selfcheck.py || echo 有回归就能当门禁用。实际扩到全量时把阅读程序题改成「捕获 stdout 再比对字符串」用contextlib.redirect_stdout包一层即可第 2 章那段capture就是现成模板。这份脚本真正的价值不在判分而在逼你把每道题的「期望值」写清楚。写不清楚的题说明那个知识点还没吃透——比如第 4 题那段循环我第一次写期望值时写了1 2 3 4 5跑出来才发现按代码是1 3 5回头重读才注意到正常分支里自增写了两次。类似的还有第 8 题id比较、第 47 题映射表大小写都是「看答案觉得懂、写用例立刻露馅」的类型。从那以后我每次拿到这类带答案的练习文档都强制先抽十条确定性结果落成断言再动手刷题宁可开头多花二十分钟也不在「以为自己会了」上反复交学费。希望帮到你。本文还有配套的精品资源点击获取