引言
这是第二章「变量、类型与运算符」的最后一篇,也是收卷篇。前十篇依次建立了:变量与引用的真相、数字类型画像、布尔与真值测试、字符串基础、f-string 格式化、类型转换、运算符优先级、位运算、命名规范。本篇做三件事:第一,用 10 道选择题把你拉回每个知识点的”精确版本”——选择题的价值不在答对,而在把你以为懂的东西逼出原形;第二,用一道大题(进制转换器)把字符串、数字、运算符、转换、命名全部串进一个可运行的程序;第三,给出两道挑战练习,为第三章流程控制热身。建议的作答方式:先闭卷写出每题的答案与理由,再对照解析——理由比答案重要,答对但理由写错,说明该篇需要回读。
测验说明
10 道题覆盖全章 10 篇,每题标注出处。选项均为单选。做完全部题目后,再阅读解析部分;解析会指出该题对应回读哪一篇、哪个小节的哪一个概念。
第 1 题(出处:第 1 篇 变量与赋值)
a = 10
b = a
a = 20
print(b)
输出是?(A)10 (B)20 (C)报错 (D)15
第 2 题(出处:第 2 篇 数字类型)
下列哪个说法正确?(A)1 / 2 的结果是整数 0 (B)1e3 的类型是 int (C)10 ** 100 的类型是 int (D)1 + 2j 是一个浮点数
第 3 题(出处:第 3 篇 布尔与真值)
sum([True, False, True]) 的值是?(A)3 (B)2 (C)[True, False, True] (D)报错,布尔不能求和
第 4 题(出处:第 4 篇 字符串基础)
len(r"\n") 与 "ab" * 3 的结果分别是?(A)1 与 "ababab" (B)2 与 "ababab" (C)2 与 "aaaabbbb" (D)1 与 "aaabbb"
第 5 题(出处:第 5 篇 f-string)
f"{3.14159:.2f}" 的输出是?(A)"3.14" (B)"3.15" (C)"3.14159" (D)报错,格式说明符不支持
第 6 题(出处:第 6 篇 类型转换)
下列哪个调用会抛出 ValueError?(A)int("10", 2) (B)float("1e3") (C)int("3.14") (D)int("0xFF", 0)
第 7 题(出处:第 7 篇 运算符与优先级)
2 ** 3 ** 2 与 -2 ** 2 的值分别是?(A)512 与 -4 (B)64 与 -4 (C)512 与 4 (D)64 与 4
第 8 题(出处:第 8 篇 位运算)
5 & 3、~5、0b1010 | 0b0101 的值分别是?(A)1、-6、15 (B)1、6、15 (C)7、-6、15 (D)1、-6、6
第 9 题(出处:第 9 篇 命名规范)
下列哪个写法正确?(A)True = 1 (B)def list(x): return x (C)9score = 100 (D)class_ = "math"
第 10 题(出处:第 6/7/8 篇 综合)
int("3A", 16)、5 >> 1、234 // 7 的值分别是?(A)58、2、33 (B)56、2、33 (C)58、3、33 (D)58、2、34
计分与回读建议
10 题每题 10 分,答完先别翻解析,按下表对照自己的分数段安排复习:8 分以下建议重读对应错题的篇目后再做挑战练习;9~10 分可以直接进实战。分数只能说明”哪些点没建起来”,回读动作才真正补齐——错题对应篇目一览:
| 题号 | 考点 | 回读篇目 |
|---|---|---|
| 1 | 变量绑定与引用 | 第 1 篇「名字是引用」、不可变对象 |
| 2 | 整数/浮点/复数识别 | 第 2 篇类型画像 + 第 6 篇隐式转换 |
| 3 | 布尔参与算术 | 第 6 篇易错点 2 |
| 4 | 原始字符串与重复 | 第 4 篇转义与字面量 |
| 5 | 格式说明符 | 第 5 篇 f-string 精度控制 |
| 6 | 字符串转数字边界 | 第 6 篇 ValueError 边界 |
| 7 | 幂的右结合与优先级 | 第 7 篇优先级表等级 4/5 |
| 8 | 位运算三运算 | 第 8 篇 &/~/| 小节 |
| 9 | 关键字与遮蔽 | 第 9 篇语法关与冲突关 |
| 10 | 进制、移位、地板除 | 第 6/7/8 篇对应小节 |
解析:每题为什么
第 1 题:A。 b = a 让 b 与 a 绑定同一个 int 对象 10;a = 20 是让 a 重新绑定到新对象 20,b 的引用不受影响。int 不可变,赋值即重绑定——这正是第 1 篇”名字是对象的引用”的直译。
第 2 题:C。 / 恒返回 float(1 / 2 是 0.5),1e3 是 float 的科学计数法字面量;只有 ** 在整数底数整数指数时保持 int,10 ** 100 是大整数。1 + 2j 是 complex。回看第 2 篇的”int/float/complex 画像”与第 7 篇”/ 恒得 float”。
第 3 题:B。 True 是 int 的子类,参与算术时当 1 用,sum 从 0 开始累加:1+0+1 = 2。这正是第 6 篇”布尔混入算术”的易错点 2。
第 4 题:B。 原始字符串 r"\n" 中的 \n 不转义,是两个字符(反斜杠加 n),长度 2;"ab" * 3 是字符串重复,得 "ababab"。回看第 4 篇的转义与原始字符串。
第 5 题:A。 .2f 表示两位小数的定点表示,3.14159 按舍入到最近得 3.14;f"{3.14159:.2f}" 输出 "3.14"。注意格式化用的是 round-to-nearest 而非截断(联系第 6 篇银行家舍入话题)。
第 6 题:C。 int() 只认整数语法,"3.14" 含小数点直接 ValueError: invalid literal for int() with base 10: '3.14'。A 的 int("10", 2) 是按二进制解释 = 2,D 的 base=0 按前缀推断 = 255,B 的 float("1e3") 合法 = 1000.0。回看第 6 篇”ValueError 边界”。
第 7 题:A。 ** 右结合:2 ** (3 ** 2) = 2 ** 9 = 512;** 优先级高于一元负号:-(2 ** 2) = -4。回看第 7 篇优先级表等级 4 与 5 及其推导。
第 8 题:A。 5 & 3:101 & 011 = 001 = 1;~5 = -5 - 1 = -6(补码推论);0b1010 | 0b0101 = 0b1111 = 15。第 8 篇的”掩码与置位”正是 & 与 |。
第 9 题:D。 True 是关键字,赋值报 SyntaxError: cannot assign to True;以数字开头(9score)非法;def list(...) 虽语法合法,但遮蔽内置 list,属第 9 篇明令避免的坑。class_ 尾部下划线绕行是标准姿势。
第 10 题:A。 int("3A", 16) = 3×16 + 10 = 58;正数右移是整除 2 的幂,5 >> 1 = 2;234 // 7:7×33 = 231,商 33,余 3。三道小题分别对应进制解析、移位、地板除,正是本篇实战要自动化的三类计算。
速转技巧:二进制与十六进制的四位一组
十进制转十六进制用不上的时候,二进制与十六进制之间的互转有省心捷径:每 4 位二进制对应 1 位十六进制(16 = 2⁴)。0b1111 对应 "F"、0b1010 对应 "A"。所以 0x3A 拆开看就是 0011 + 1010,反过来 11111111 从右往左每四位一组 1111 1111,直接读出 0xFF。这个”8421 码”(8+4+2+1 构成一位十六进制)让你在调二进制协议时不用拿起计算器:
# 8421 速转验证:0b1011_1100 == 0xBC
for nibble_value, want in (("1011", "B"), ("1100", "C")):
got = hex(int(nibble_value, 2))[2:].upper()
print(nibble_value, "->", got, got == want) # 1011 -> B True
八进制与二进制的对应关系则是三位一组(8 = 2³),同理可推。这个技巧是第 8 篇”位是数据的原生形态”的延续——字节(8 位)正好两个十六进制位,网络抓包工具里清一色的十六进制显示,根源在此。
综合实战:进制转换器
选择题到此为止,现在进入”能跑起来的测验”:一个支持2/8/10/16 进制互转的交互式命令行工具。它把全章知识用了个遍——字符串切片(第 4 篇)、整除与取余 divmod(第 7 篇)、try/except 的 ValueError 边界(第 6 篇)、命名规范(第 9 篇)。
核心函数:十进制转任意进制
转换算法一句话:反复除以目标进制,余数倒序排列。整数除法留商,% 留余——这正是第 7 篇 a == (a // b) * b + a % b 恒等式的工程应用:
DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def to_base(n: int, base: int) -> str:
"""十进制整数转 2~36 进制字符串,支持负数。"""
if not 2 <= base <= 36:
raise ValueError(f"不支持的进制:{base}")
sign = "-" if n < 0 else ""
n = abs(n)
digits = []
while n:
n, r = divmod(n, base)
digits.append(DIGITS[r])
return sign + "".join(reversed(digits)) if digits else "0"
DIGITS 是常量(全大写,第 9 篇),下标即数值:DIGITS[10] 是 "A"。divmod 一次给商与余,reversed 倒序拼装,n = 0 时返回 "0"。36 进制封顶是因为 DIGITS 恰好 36 个字符——与 int(s, base) 的 base 上限(2~36)严格一致。
反向:任意进制字符串转十进制
def from_base(s: str, base: int) -> int:
"""2~36 进制字符串转十进制整数,逐字符合法校验。"""
s = s.strip()
sign = 1
if s.startswith("-"):
sign, s = -1, s[1:]
elif s.startswith("+"):
s = s[1:]
if not s:
raise ValueError("输入为空")
value = 0
for ch in s.upper():
d = DIGITS.find(ch)
if d < 0 or d >= base:
raise ValueError(f"字符 '{ch}' 不是 {base} 进制数字")
value = value * base + d
return sign * value
逐位累加是霍纳法则(Horner’s method):"3A" 读作 ((3) × 16 + 10)。DIGITS.find(ch) 返回下标即数值,find 找不到返回 -1,恰好与”非法字符”判定合体。d >= base 拦截”数字存在但进制太小”的情形——"2" 在二进制里非法,正如测验第 6 题的边界讨论。
主循环:交互式回收站
def convert():
"""交互主循环:源进制 -> 数字 -> 目标进制。"""
while True:
cmd = input("请输入源进制(2/8/10/16,q 退出): ").strip().lower()
if cmd == "q":
print("再见!")
return
if cmd not in ("2", "8", "10", "16"): # 白名单校验
print("仅支持 2/8/10/16 进制")
continue
src = int(cmd)
raw = input("请输入要转换的数字: ").strip()
try:
value = from_base(raw, src)
except ValueError as e:
print(f"输入无效:{e}") # 非法数字不崩溃
continue
dst = input("请输入目标进制(2/8/10/16): ").strip()
if dst not in ("2", "8", "10", "16"):
print("仅支持 2/8/10/16 进制")
continue
print(f"结果:{raw} (base {src}) = {to_base(value, int(dst))} (base {dst})")
if __name__ == "__main__":
convert()
配置文件或命令行程序的标准骨架:白名单校验(cmd not in (...),第 3 篇成员测试)→ 转换 → 异常兜底(try/except ValueError,第 6 篇边界)→ 格式化输出(f-string,第 5 篇)。一串真实运行记录(Python 3.12 实测):
请输入源进制(2/8/10/16,q 退出): 16
请输入要转换的数字: FF
请输入目标进制(2/8/10/16): 10
结果:FF (base 16) = 255 (base 10)
请输入源进制(2/8/10/16,q 退出): 10
请输入要转换的数字: 255
请输入目标进制(2/8/10/16): 2
结果:255 (base 10) = 11111111 (base 2)
请输入源进制(2/8/10/16,q 退出): 8
请输入要转换的数字: FF
输入无效:字符 'F' 不是 8 进制数字
请输入源进制(2/8/10/16,q 退出): 10
请输入要转换的数字: -42
请输入目标进制(2/8/10/16): 16
结果:-42 (base 10) = -2A (base 16)
请输入源进制(2/8/10/16,q 退出): q
再见!
注意三条细节:非法输入被拦截而不是崩溃;负数全程保留符号位;空输入在 from_base 里被 ValueError("输入为空") 拦下。这些行为全部来自第 6 篇的 ValueError 边界讨论。
双向一致性自检
测验性质的程序最怕”看起来对”。to_base 与 from_base 互为逆运算,可以交叉验证——每个数转出去再转回来必须原样:
for base in (2, 8, 10, 16, 36):
for n in (0, 255, -255, (1 << 32) - 1):
assert from_base(to_base(n, base), base) == n
print("全部 20 组自检通过") # 5 种进制 × 4 个样例
assert(断言)让不一致直接中断,是第 3 篇”验证型代码”的又一例证。样例特意包含 0、负数与大整数——0 走”空数字”分支,负数走符号分支,(1 << 32) - 1 是 4294967295(第 8 篇位运算的 32 位全 1),覆盖最大位宽。
诚实地审视这个”成品”
转换器能跑,但要承认它的两个工程短板,这本身就是本章该有的批判眼光:其一,它没有复用 int(s, base) 与 format()——标准库做同样的事只需一行(format(255, "b") 得 "11111111");自己实现的价值在理解机制而不在生产效率,真实项目里优先用内置,眼里有实现才是进阶者的状态。其二,输入体验粗糙:目标进制校验在读取之后才做、没有历史记录、非法输入只提示不引导——这些在第三章学到循环与字符串方法后都可以补。工具能用的判断标准不是”写出来了”,而是”边界都测过了”:把测验第 10 题的三个数值喂给它,再想想还有什么输入会击穿它。
挑战练习
三道题按难度递进,全部用本章 API 就能完成。第一题复习补码观察法:n & ((1 << bits) - 1) 把无限位宽的负数截断成 bits 位,正是第 8 篇”掩码截断”技巧;第二题把 to_base、字符串切片与回文判断组合,验证 585 这个”跨进制的巧合”;第三题没有现成答案,强迫你拆解十进制小数的位权结构——如果只靠复制粘贴学到这里,这题会卡住你,而这正是它存在的目的。做完再回头读一遍解析,两轮走完,本章的知识点才算真正交割。
- 补码视图:写
twos_complement_view(n, bits=8),返回 n 在 bits 位补码下的二进制字符串(提示:n & ((1 << bits) - 1))。验证twos_complement_view(-6)是"11111010"。 - 跨进制回文:写
is_palindrome_num(n, base),判断 n 在给定进制下是否为回文(左右对称)。已知 585 的二进制1001001001是回文,验证之。 - 扩展思考:如果把
from_base的合法输入扩展到二进制小数(如"101.1"表示 5.5),算法与DIGITS需要哪些改动?先口头设计,再实现(提示:小数点两侧分别处理,右侧的权重是负次幂)。
小结
本章十条主线在今天完成闭环:绑定与引用是第一块地基;数字、布尔、字符串三类基本类型各有各的语义;f-string 让输出精确可控;显式转换与隐式转换管着”类型之门”;优先级表是表达式的宪章;位运算把整数拆回比特;命名规范让一切努力可维护。进制转换器把其中的字符串、数字、运算符、转换、异常五条线织在一起,而它离”真实工具”还差最后一公里——流程控制。第三章「流程控制」将从 if/elif/else 与 for/while 讲起,把今天写在主循环里的 while True 和 continue 正式扶正;读到这里,你写的每段代码都已经具备本章的全部判断力。用测验里的错题做复习清单,我们第三章见。