📖 配套主手册《Python 碎片化复习手册 · CPython 3.14》
🎯 训练规模55 个核心知识训练组 · 覆盖变体 / Bug / 面试追问(另含 55 道高频速查题与专题路径收口题)
💡 使用建议:自测题答案默认折叠,建议先在脑海或草稿中尝试作答,再点击展开核对。答错或不稳时可一键回查主手册对应知识卡。

使用规则

  1. 先在主手册把对应卡至少学到 M1。
  2. 变体题训练 M2:改变条件后预测行为。
  3. 真实开发 Bug训练 M3:从症状定位机制并给修复方向。
  4. 面试追问训练 M3~M4:解释边界、反例和工程权衡。
  5. 答错时不要直接背答案;先根据调度 metadata 的 rollback 回退前置卡。

专题路径收口题

专题路径读完后,不要立刻换下一条。先合上正文,回答下面对应的一题。这里故意不直接给完整答案;答不稳时按“参考卡”回查,而不是从头重读。

路径 收口题 参考卡
对象模型 a = [[1]]; b = a.copy(); b[0].append(2) 后,画出 ab、外层 list、内层 list 的对象关系图。 69
函数参数 一个函数先 items.append(1),再 items = []。调用方最后观察到什么?为什么两个语句影响不同? 13
作用域 / 闭包 解释为什么 lambda: i 在循环结束后通常都得到最终 i,以及 lambda i=i: i 为什么能修复。 2225
装饰器 不使用 @ 语法,把一个带参数装饰器的绑定过程完整写出来,并指出闭包保存了哪些状态。 242627
容器 / 复杂度 你需要对 10 万条记录按 id 做 10 万次 membership/query。说明何时应从 list 转成 dict/set,以及构建成本为什么值得。 3438166
迭代 / 生成器 为什么 list(generator) 后再次 list(generator) 常得到空列表?如果需要两次完整遍历,设计上有哪些选择? 5254
OOP / Data Model 解释 obj.method 为什么能自动绑定 self,并说明实例属性、data descriptor、non-data descriptor 的查找优先级。 586566
Import A import B,B 又从 A 导入一个尚未定义的名字。用 sys.modules 和模块初始化顺序解释失败。 7980
Typing 为什么 def f(x: int): ... 仍可在运行时收到字符串?那静态类型检查到底替你解决什么问题? 82
Thread / GIL 一个“先读余额、判断、再扣款”的多步操作,在传统 GIL CPython 下为什么仍需要同步?free-threaded 下又有什么变化? 110113114
Asyncio 一个 async handler 内调用 time.sleep(5)。从 event loop 角度解释其他请求为什么也会被拖住,并给出两个修复方向。 115119
GC / 生命周期 画出两个对象互相引用、外部名字都被删除后的对象图,并解释引用计数和 cyclic GC 各负责什么。 124125
subprocess / 安全 为什么 shell=True + 用户字符串是危险组合?仅改成 argv list 后还剩哪些输入安全问题? 144182
数据库 一个请求更新三张表,第二张成功后第三张失败。说明 transaction 边界、rollback 和 connection pool 分别解决什么问题。 150151
测试 / Mock 被测模块 from client import send 后调用 send()。你 patch 哪个名字?再说明 Stub 与 Mock 的区别。 158159
性能 一个接口慢 800ms,profiling 发现 Python 计算只占 20ms,却执行了 101 次 SQL。你先优化哪里?为什么? 167170
Packaging 从源码仓库到 pip install,用 pyproject.toml → build backend → wheel/sdist → installer 串一次完整链路。 174175
安全 分别判断“不可信字符串进入 eval”“进入 shell”“作为随机 token 来源”三种场景的第一安全边界。 181182185

30 秒随机复习

这一节只作为随机入口。答案默认折叠/跳回主卡,避免滚动时提前看到。


下面这些问题非常适合日常随机抽查。建议按“先答 → 标记置信度 → 再核对”的方式使用:

2 = 能解释原因,并能预测一个变体
1 = 只记得结论,原因不稳
0 = 想不起来 / 判断错

这里的 0/1/2 只是本次作答结果,不是掌握度;长期状态仍只使用 U/M0~M4

优先复习 0 分卡,其次 1 分卡;连续两次得到 2 分后再拉长复习间隔。能不用运行代码直接解释,并能处理一个小变体,才说明心智模型在变稳。

  1. a = b 为什么通常不是复制对象?
  2. is== 的区别是什么?
  3. 为什么 tuple 里仍然可以“看到内部 list 被修改”?
  4. 为什么 mutable default argument 会跨调用共享?
  5. 参数传递为什么不能简单说“list 引用传递、int 值传递”?
  6. globalnonlocal 分别改变哪层绑定?
  7. closure 为什么能在 outer 返回后继续访问状态?
  8. late binding 到底绑定的是什么?
  9. @decorator 的等价展开是什么?
  10. 为什么 functools.wraps 值得用?
  11. bytesstr 的边界是什么?
  12. append()extend() 差别是什么?
  13. dict 为什么平均查找接近 O(1)?
  14. dict 为什么保持插入顺序但仍然是哈希表?
  15. and/or 为什么不一定返回 bool?
  16. loop else 什么时候执行?
  17. iterable 与 iterator 有什么区别?
  18. generator 为什么通常只能消费一次?
  19. yield 保存了什么执行状态?
  20. self 为什么会被自动传入实例方法?
  21. super() 为什么不是简单“父类对象”?
  22. descriptor 与 property、method binding 有什么关系?
  23. finally 为什么适合清理资源?
  24. 为什么不应吞掉 Exception
  25. import 为什么会缓存 module object?
  26. circular import 为什么经常是架构信号?
  27. Type Hint 为什么不自动做 runtime validation?
  28. Anyobject 有什么区别?
  29. Python 3.14 的注解求值发生了什么变化?
  30. 文本文件为什么应明确 encoding?
  31. pickle.loads() 为什么不能处理不可信数据?
  32. thread、process、coroutine 分别在哪个层次?
  33. GIL 为什么不等于线程安全?
  34. Python 3.14 free-threaded 为什么不能直接理解成“所有 Python 都无 GIL”?
  35. async function 调用为什么得到 coroutine object?
  36. blocking call 为什么会卡 event loop?
  37. TaskGroup 比裸 create_task 生命周期管理好在哪里?
  38. 引用计数为什么解决不了循环引用?
  39. 对象释放后 RSS 为什么不一定下降?
  40. dis 能回答什么、不能保证什么?
  41. subprocess 为什么优先 argument list?
  42. 网络请求为什么一定要考虑 timeout?
  43. retry 为什么必须考虑幂等性?
  44. transaction 边界为什么是业务设计问题?
  45. fixture 与 mock 分别解决什么问题?
  46. patch 为什么要 patch 使用位置?
  47. coverage 为什么不能等价测试质量?
  48. profiler 与 benchmark 的问题分别是什么?
  49. cache 为什么会延长对象生命周期?
  50. virtual environment 为什么不是安全 sandbox?
  51. wheel 和 sdist 有什么区别?
  52. formatter、linter、type checker 为什么不是同一种工具?
  53. secrets 为什么比 random 更适合 token?
  54. EAFP 与 LBYL 什么时候各自更清楚?
  55. composition 为什么经常比 inheritance 更容易演进?


自测答案与主知识卡索引

展开 55 道简短答案

建议先口头回答,再展开这里核对。答案刻意保持短,只用于校准心智模型。

  1. 赋值通常只新增/改变名字到对象的绑定,不自动复制对象;复制需要显式 copy/deepcopy。 → 卡 6
  2. == 关注相等性语义,is 关注对象 identity;普通值比较通常用 ==。 → 卡 8
  3. tuple 不允许替换自己保存的引用,但它引用的可变 list 仍可被原地修改。 → 卡 7
  4. 默认参数表达式在函数定义时求值;默认 list/dict 会被多次无参调用复用。 → 卡 16
  5. 参数模型对类型是一致的:形参名绑定到传入对象;差异来自对象可变性以及函数执行 mutate 还是 rebind。 → 卡 13
  6. global 指向模块全局绑定;nonlocal 指向最近的 enclosing function scope 绑定。 → 卡 23
  7. closure 保存了对 free variable 所在 cell/环境的引用,因此 outer frame 结束后相关对象仍可存活。 → 卡 24
  8. late binding 绑定的是变量/名称所在 cell,而不是在创建 lambda 时自动冻结当时的值。 → 卡 25
  9. @decorator 可理解为定义函数后执行 func = decorator(func)。 → 卡 26
  10. wraps 复制/维护 __name____doc____wrapped__ 等元数据,利于调试、内省和工具链。 → 卡 26
  11. str 表示 Unicode 文本;bytes 表示原始字节。文本进出二进制边界需要显式 encode/decode。 → 卡 31
  12. append(x) 把 x 作为一个元素加入;extend(iterable) 逐个加入 iterable 的元素。 → 卡 34
  13. dict 基于哈希表,平均情况下 hash 定位让查找/插入接近 O(1),最坏情况不能机械保证。 → 卡 38
  14. “保持插入顺序”描述迭代顺序;底层仍可使用哈希表维护 key 定位,两者不矛盾。 → 卡 38
  15. and/or 返回参与短路求值的操作数之一,而不是强制转换成 bool。 → 卡 44
  16. 循环正常耗尽且没有被 break 终止时执行 else。 → 卡 49
  17. iterable 能产生 iterator;iterator 还保存迭代状态并实现 __next__()。 → 卡 52
  18. generator object 本身就是 iterator,消费会推进内部状态;耗尽后不会自动重新开始。 → 卡 54
  19. yield 暂停 frame,保留指令位置、局部变量和执行上下文,以便下次恢复。 → 卡 54
  20. 函数对象是 descriptor;通过实例访问时 __get__() 产生 bound method,把实例预绑定为第一个参数。 → 卡 58
  21. super() 根据 MRO 从当前位置继续查找,不是“直接拿到父类对象”。 → 卡 61
  22. descriptor 控制属性访问;property 是 data descriptor,普通方法绑定也依赖函数 descriptor。 → 卡 66
  23. finally 无论正常返回还是异常传播通常都会运行,适合释放资源和恢复状态。 → 卡 73
  24. 过宽捕获后不处理会隐藏真实失败、破坏可观测性;只捕获能正确处理的异常。 → 卡 69
  25. 成功 import 后 module object 会进入 sys.modules,后续 import 通常复用它,避免重复执行模块顶层代码。 → 卡 79
  26. 它常暴露模块初始化顺序互相依赖和职责耦合;可以通过重新划分依赖方向或延迟导入解决。 → 卡 80
  27. Type Hint 主要给静态 checker/IDE/人阅读,Python 运行时默认不会按注解自动拒绝不匹配参数。 → 卡 82
  28. object 表示“某个具体但未知的普通对象”,使用前需缩窄;Any 会让静态检查器放弃很多检查。 → 卡 84
  29. 3.14 默认从 eager 变为 deferred evaluation;但 from __future__ import annotations 仍是字符串化语义。 → 卡 91
  30. 文本必须经编码映射到字节;不显式 encoding 会依赖环境默认值,导致跨机器/区域不稳定。 → 卡 92
  31. pickle 反序列化可以触发对象构造/代码路径,不是只读取“纯数据”;不可信输入可导致代码执行风险。 → 卡 96
  32. process 是 OS 资源/地址空间单位;thread 是进程内执行流;coroutine 是由语言/事件循环协作调度的可暂停计算。 → 卡 140
  33. GIL 只限制默认 CPython 同一时刻执行 Python 代码的线程数量,不保护你的多步骤业务不变量,也不消除 race condition。 → 卡 113
  34. free-threaded 是可选构建;GIL 还可能被显式启用或因不兼容 C extension 在运行时重新启用。 → 卡 114
  35. 调用 async def 不立即跑完整函数体,而创建 coroutine object;需要 await/Task 驱动它。 → 卡 116
  36. event loop 依赖任务主动让出执行权;同步阻塞调用不 await,会占住 loop 线程。 → 卡 119
  37. TaskGroup 把子任务生命周期绑定在结构化作用域内,并统一处理等待、失败和取消传播。 → 卡 117
  38. 循环中的对象彼此引用,即使外部不可达,单纯 refcount 也可能都不为 0,需要 cyclic GC 识别不可达对象图。 → 卡 125
  39. 对象释放只说明对象生命周期结束;allocator、分片、QSBR/mimalloc purge 或 OS 策略都可能让 RSS 暂时不降。 → 卡 128
  40. dis 能观察当前 CPython 字节码/指令形态;它不能保证跨版本/跨实现稳定,也不能单独证明高层语义性能。 → 卡 132
  41. argument list 避免 shell 再解析字符串;但还要防目标 CLI 自身的 option injection。 → 卡 144
  42. 网络可能无限慢、半连接或对端不响应;没有 timeout 就可能无限占用线程/任务/连接。 → 卡 148
  43. 重试会重复执行请求;非幂等操作可能重复扣款/创建资源,因此要设计 idempotency key 或幂等语义。 → 卡 148
  44. 事务决定哪些业务状态必须原子提交/回滚,边界过大或过小都会影响一致性、锁和并发。 → 卡 150
  45. fixture 提供可复用测试环境/依赖;mock/test double 替代边界依赖并观察交互,目标不同。 → 卡 156
  46. patch 应替换“被测代码实际查找该名字的位置”,而不是机械 patch 对象最初定义的位置。 → 卡 159
  47. coverage 只说明哪些代码被执行,不证明断言充分、边界完整或行为正确。 → 卡 161
  48. benchmark 回答“这一段/方案多快”;profiler 回答“时间/资源主要花在哪里”。 → 卡 167
  49. cache 本质保留对象引用以便复用,所以会延长被缓存 key/value 的生命周期并增加内存占用。 → 卡 169
  50. venv 隔离 Python package 依赖,不隔离 OS 权限、文件、网络、进程等安全边界。 → 卡 172
  51. wheel 是预构建 distribution 格式;sdist 是源代码分发,需要目标环境执行构建流程。 → 卡 175
  52. formatter 统一格式;linter 找风格/潜在错误;type checker 做静态类型分析,三者问题域不同。 → 卡 179
  53. random 面向模拟/一般随机;secrets 使用适合安全 token/凭证场景的随机源和 API。 → 卡 185
  54. EAFP 在异常确实代表少见失败且竞争窗口存在时清楚;LBYL 在检查本身便宜、语义明确且不是竞态检查时可读。 → 卡 189
  55. composition 通过显式对象协作组合行为,依赖面更窄;inheritance 把子类绑到父类行为/MRO,演进时耦合通常更强。 → 卡 192

核心知识训练中心

这一篇不新增知识点,而是把 55 张高频核心卡转换成四种能力测试:

基础题      → 能否复述/预测核心规则
变体题      → 条件变化后还能否守住边界
真实开发 Bug → 能否从症状定位到机制并给出修复方向
面试追问    → 能否用准确术语解释原理、权衡与边界

建议不要一次刷完。每次选 1~3 张:先做变体预测;需要训练排错时做真实 Bug;准备面试或冲击 M4 时再做面试追问。每个训练块都可跳回对应主知识卡。

30.1 训练索引

训练 主题 回到正文
004 Python 对象:identity、type、value 原卡
006 引用与对象共享 原卡
007 Mutable 与 Immutable 原卡
008 is== 原卡
009 浅拷贝与深拷贝 原卡
013 Python 参数传递模型 原卡
014 完整参数语法 原卡
016 默认参数在定义时求值 原卡
022 LEGB 名称查找 原卡
023 globalnonlocal 原卡
024 闭包 Closure 原卡
025 闭包的 Late Binding 原卡
026 装饰器的本质 原卡
031 str、Unicode、编码与解码 原卡
034 List:动态数组心智模型 原卡
038 Dict:哈希表与顺序 原卡
044 Truthiness 与短路求值 原卡
047 for 的真实基础:迭代协议 原卡
052 Iterable 与 Iterator 原卡
054 Generator 与 yield 原卡
057 Class、Instance 与 Attribute 原卡
058 self、实例方法、classmethod、staticmethod 原卡
061 MRO 与 super() 原卡
065 Attribute Lookup、__getattr____getattribute__ 原卡
066 Descriptor 原卡
073 Context Manager 与 with 原卡
079 sys.modules 与 Import Cache 原卡
080 Circular Import 原卡
082 Type Hint 不改变动态类型本质 原卡
106 Concurrency 与 Parallelism 原卡
110 Race Condition 与 Lock 原卡
113 传统 GIL 心智模型 原卡
114 Free-threaded CPython 3.14 原卡
115 Asyncio 核心模型 原卡
117 Task、TaskGroup 与结构化并发 原卡
119 Blocking Code 会卡 Event Loop 原卡
124 Reference Counting 原卡
125 循环引用与 Cyclic GC 原卡
144 subprocess 原卡
148 Timeout、Retry 与 Idempotency 原卡
150 Transaction 原卡
156 Fixture 原卡
158 Dummy、Stub、Spy、Mock、Fake 原卡
159 Patch 的位置 原卡
166 Big O 与 Python 容器复杂度 原卡
169 Cache 原卡
170 N+1、批处理与 IO 性能 原卡
174 pyproject.toml 原卡
175 Wheel 与 Source Distribution 原卡
181 eval()exec() 原卡
182 Command Injection 原卡
185 random vs secrets 原卡
189 EAFP 与 LBYL 原卡
190 Pure Function 与 Side Effect 原卡
192 Composition vs Inheritance 原卡

30.2 四档训练

训练 004 · Python 对象:identity、type、value

↩ 返回知识卡 4

基础题
a = [1, 2]
b = [1, 2]
c = a

不运行代码,判断 a == ba is ba is c

揭晓: True / False / True== 讨论值;is 讨论对象身份。

变体题

为什么不能根据 id(a) 的某个具体数字去推断对象的物理内存地址?

揭晓: id() 保证的是对象生命周期内的 identity 值;CPython 常把它实现得像地址,但这是实现细节,不是 Python 语言承诺。

真实开发 Bug

缓存层用 payload is cached_payload 判断“数据有没有变化”,结果相同内容但重新解析出的 dict 总被判为变化。修复应改成什么?

诊断: 业务想比较 value,应使用合适的 == 或业务字段/哈希,而不是 identity。

面试追问

“Python 一切皆对象”对函数、类、模块意味着什么?

回答要点: 它们都可以被绑定名称、传参、返回、存入容器;这直接支撑高阶函数、装饰器、反射与元编程。


训练 006 · 引用与对象共享

↩ 返回知识卡 6

基础题
a = {"items": []}
b = a
b["items"].append(1)

a 是什么?

揭晓: {"items": [1]},因为 ab 引用同一个 dict,内部又引用同一个 list。

变体题
a = []
b = [a, a]
b[0].append(1)

b 的两个元素是什么关系?

揭晓: 两个槽位都引用同一个 list,所以 b == [[1], [1]]

真实开发 Bug

配置模板 dict 被多个请求复用,请求 A 在嵌套 headers 中添加字段后,请求 B 也看到了。根因是什么?

诊断: 共享了可变对象图;需要明确每请求对象边界,必要时重建或复制对应层级。

面试追问

“容器装的是对象还是对象引用?”如何用这个模型解释嵌套对象共享?

回答要点: Python 容器保存的是对对象的引用;外层对象独立不代表内部对象也独立。


训练 007 · Mutable 与 Immutable

↩ 返回知识卡 7

基础题
a = [1]
b = a
a += [2]

a is b 是什么?

揭晓: 通常为 True;list 的 += 是原地扩展语义。

变体题
a = ([1],)
a[0].append(2)

为什么 tuple “不可变”却能看到变化?

揭晓: tuple 自己保存的引用没变;变化发生在它引用的可变 list 内部。

真实开发 Bug

开发者认为 tuple 配置绝对安全,把可变 dict 放进 tuple 后仍被其他代码修改。问题在哪?

诊断: immutable 只约束对象自身结构,不递归保证其引用对象不可变。

面试追问

可变性为什么会影响 hashability、默认参数和并发共享状态?

回答要点: 原地状态变化会破坏稳定哈希、跨调用共享默认对象、以及并发读改写的一致性。


训练 008 · is==

↩ 返回知识卡 8

基础题

什么时候应优先写 x is None

揭晓: 判断是否就是 None 单例时;这表达 identity 语义,也不受自定义 __eq__ 影响。

变体题
a = 256
b = 256
print(a is b)

为什么即使某次运行得到 True,代码也不能依赖它?

揭晓: 小整数/常量复用属于实现与编译上下文优化;值比较应使用 ==

真实开发 Bug

生产代码写 status is "ready",测试偶尔通过、换构建环境后失败。如何 review?

诊断: 字符串值比较必须用 ==;identity 不能承担业务值语义。

面试追问

自定义 __eq__ 后,is 会受影响吗?

回答要点: 不会;is 是对象身份操作,不能被重载。


训练 009 · 浅拷贝与深拷贝

↩ 返回知识卡 9

基础题
import copy
a = [[1]]
b = copy.copy(a)
b[0].append(2)

a 是什么?

揭晓: [[1, 2]];浅拷贝只复制外层 list。

变体题

为什么 deepcopy() 不是“任何时候都更安全”?

揭晓: 它可能复制不该复制的共享语义、代价高,并且外部资源/锁/连接等对象并不适合被无脑深拷贝。

真实开发 Bug

Web 请求先 cfg = DEFAULT.copy(),随后修改 cfg["db"]["timeout"],全局默认值也变了。

诊断: dict.copy() 是浅拷贝,嵌套 db dict 仍共享;应只重建需要独立的子结构或使用明确的数据模型。

面试追问

deepcopy() 怎么避免面对循环引用时无限递归?

回答要点: 实现会维护已经复制过的对象映射(memo),复用已创建的副本。


训练 013 · Python 参数传递模型

↩ 返回知识卡 13

基础题
def f(items):
    items.append(1)
    items = []

a = []
f(a)

最终 a 是什么?

揭晓: [1]append 修改共享对象;之后 items = [] 只是重新绑定局部名字。

变体题

为什么“list 是引用传递、int 是值传递”是一个危险简化?

揭晓: 参数绑定规则统一;差异来自对象可变性以及函数执行的是 mutate 还是 rebind。

真实开发 Bug

工具函数 normalize(data) 为了“方便”直接修改传入 dict,调用方复用原始数据时出现状态污染。

诊断: API 没有明确 mutation contract;要么返回新对象,要么在命名/文档中清楚声明会原地修改。

面试追问

用“名称绑定”而非“值/引用二选一”解释一次函数调用。

回答要点: 实参表达式先得到对象,形参是在新局部作用域建立的名称绑定;多个名称可能共享对象。


训练 014 · 完整参数语法

↩ 返回知识卡 14

基础题
def f(a, /, b=1, *args, c, **kwargs):
    return a, b, args, c, kwargs

f(10, 20, 30, c=40, x=50) 返回什么?

揭晓: (10, 20, (30,), 40, {"x": 50})

变体题

为什么 API 设计中 timeout 常适合做 keyword-only?

揭晓: 调用 request(url, timeout=3) 比位置值 request(url, 3) 更清楚,且将来扩展签名更稳。

真实开发 Bug

公共库大量暴露 def api(*args, **kwargs),IDE 无法补全,参数拼错只能运行时发现。

诊断: 过度吞掉签名;仅在包装/转发等确有需要时使用,并尽量保留可检查的签名与类型信息。

面试追问

/* 对 API 兼容性分别有什么价值?

回答要点: / 让参数名不成为调用契约;* 强制关键字调用,提高语义清晰度并降低位置参数扩展风险。


训练 016 · 默认参数在定义时求值

↩ 返回知识卡 16

基础题
def f(x=[]):
    x.append(1)
    return x

print(f())
print(f())

揭晓: [1][1, 1]

变体题
from datetime import datetime

def stamp(now=datetime.now()):
    return now

为什么它也有同类问题,即使默认值不是 list?

揭晓: datetime.now() 在函数定义时只执行一次;这里的问题是“求值时机”,不只“可变性”。

真实开发 Bug

定时任务函数默认 started_at=datetime.now(),日志里一天内大量任务拥有完全相同开始时间。

诊断: 改用 None/sentinel,在调用时生成动态值。

面试追问

默认参数为什么有时可以故意用可变对象?为什么仍通常不推荐?

回答要点: 可利用定义时单次求值保存状态/缓存;但语义隐蔽、难测试、易造成跨调用耦合。


训练 022 · LEGB 名称查找

↩ 返回知识卡 22

基础题

函数内部读取 name 时,LEGB 的查找顺序是什么?

揭晓: Local → Enclosing → Global → Builtins。

变体题
x = 10
def f():
    print(x)
    x = 20

为什么不是先打印全局 10

揭晓: 编译函数体时 x = 20 使 x 被判定为局部名称;读取发生在局部赋值前,会触发 UnboundLocalError

真实开发 Bug

模块写了 list = [],下方代码调用 list(iterable) 报错。

诊断: Global 名称遮蔽 Builtins;避免 shadow builtin。

面试追问

LEGB 是“运行时逐层扫描所有字典”这么简单吗?

回答要点: 这是语言层心智模型;CPython 会在编译阶段分类 local/free/global 名称,并生成不同字节码访问路径。


训练 023 · globalnonlocal

↩ 返回知识卡 23

基础题

global xnonlocal x 分别改变哪个作用域中的绑定?

揭晓: global 指向模块全局绑定;nonlocal 指向最近的 enclosing function scope 绑定。

变体题

为什么 nonlocal 不能创建一个此前不存在的 enclosing 名称?

揭晓: 它要求在外层函数作用域中已经存在可绑定名称,否则无法解析目标 cell。

真实开发 Bug

一个闭包计数器忘写 nonlocal count,执行 count += 1 时触发 UnboundLocalError

诊断: 赋值让 count 被认作当前局部;应显式 nonlocal count 或改成更清晰的状态对象。

面试追问

什么时候应该避免 global/nonlocal,即使语法允许?

回答要点: 当共享可变状态让依赖隐蔽、测试困难或并发风险升高时,优先显式传递状态或封装对象。


训练 024 · 闭包 Closure

↩ 返回知识卡 24

基础题
def make_add(n):
    def add(x):
        return x + n
    return add

make_add(10) 返回后,为什么 n 仍可被访问?

揭晓: 返回函数形成 closure,保留了对 free variable n 所在 cell 的引用。

变体题

闭包“保存变量”是否等于把当时的值复制一份?

揭晓: 不一定。闭包保存的是与自由变量绑定相关的 cell;如果绑定后来改变,闭包可能观察到变化。

真实开发 Bug

工厂函数返回几十个 handler,每个闭包都捕获大型配置对象,导致配置长期不能释放。

诊断: closure 延长了被捕获对象生命周期;只捕获真正需要的小状态,或改为显式对象/参数。

面试追问

闭包和 callable class 怎么选?

回答要点: 少量封闭状态和简单行为适合闭包;需要多个方法、显式状态、可检查生命周期时 class 更清楚。


训练 025 · 闭包的 Late Binding

↩ 返回知识卡 25

基础题
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])

揭晓: 通常 [2, 2, 2];调用时才从 closure cell 读取最终 i

变体题

为什么 lambda i=i: i 能修复?

揭晓: 默认参数表达式在函数创建时求值,把当次 i 绑定进每个函数自己的默认参数。

真实开发 Bug

循环注册三个按钮回调,点击任一按钮都处理最后一个 ID。

诊断: late binding;可以用默认参数冻结值、functools.partial,或创建新的工厂作用域。

面试追问

Late binding 是 lambda 特有问题吗?

回答要点: 不是;普通嵌套 def 捕获同一个循环变量也一样。


训练 026 · 装饰器的本质

↩ 返回知识卡 26

基础题
@trace
def f():
    pass

不用 @ 写出等价绑定。

揭晓: 先创建原函数,再执行 f = trace(f)

变体题

为什么装饰器通常在模块 import/函数定义阶段执行,而 wrapper 在函数调用时执行?

揭晓: 装饰表达式参与函数对象创建后的重新绑定;返回的 wrapper 才是以后真正被调用的对象。

真实开发 Bug

自定义装饰器没用 functools.wraps,框架拿到的函数名、docstring、annotation 都变成 wrapper 的。

诊断: 元数据丢失;用 @wraps(func),必要时还要考虑签名保持。

面试追问

装饰器和显式函数组合相比,最大的设计风险是什么?

回答要点: 行为被隐式包裹,调用链/异常/性能/状态可能不透明;应控制层数并保持单一职责。


训练 031 · str、Unicode、编码与解码

↩ 返回知识卡 31

基础题

str.encode()bytes.decode() 的方向分别是什么?

揭晓: str → bytes 是 encode;bytes → str 是 decode。

变体题

同一个字符的 len(text)len(text.encode("utf-8")) 为什么可能不同?

揭晓: 前者按 Unicode code point 数量计,后者按编码后的字节数计。

真实开发 Bug

服务从网络收到 UTF-8 bytes,却直接和 Python str 比较,导致条件永远不匹配或类型错误。

诊断: 明确 text/binary boundary,在 IO 边界只解码一次,内部尽量统一使用 str

面试追问

“Unicode”和“UTF-8”是什么关系?

回答要点: Unicode 定义字符/码点体系;UTF-8 是将 Unicode code point 编码成 bytes 的一种编码方案。


训练 034 · List:动态数组心智模型

↩ 返回知识卡 34

基础题

为什么 list.append() 平均是 O(1),但不是每次严格 O(1)?

揭晓: list 预留容量;多数 append 只写入槽位,偶尔需要扩容并复制引用,属于摊销 O(1)。

变体题

为什么在 list 头部不断 insert(0, x) 通常是坏选择?

揭晓: 现有元素引用需要整体移动,单次接近 O(n);队列头尾操作通常考虑 deque

真实开发 Bug

消费者用 items.pop(0) 处理几十万任务,CPU 时间随队列长度增长。

诊断: 数据结构与访问模式不匹配;改用 collections.deque.popleft()

面试追问

list 中保存的是对象本体还是引用?这如何影响复制和内存估算?

回答要点: 主要是对象引用数组;sys.getsizeof(list) 不递归包含被引用对象总大小。


训练 038 · Dict:哈希表与顺序

↩ 返回知识卡 38

基础题

为什么 dict 查找平均接近 O(1)?

揭晓: 对 key 计算 hash 后定位哈希表槽位,通常无需线性扫描全部键。

变体题

两个对象 a == b 且都可哈希时,hash(a)hash(b) 应满足什么关系?

揭晓: 必须相等;否则会破坏哈希容器的等价性约束。

真实开发 Bug

自定义 key 对象实现了基于可变字段的 __hash__,插入 dict 后字段改变,随后“明明在 dict 里却查不到”。

诊断: hash/equality 参与字段必须在作为 key 期间保持稳定。

面试追问

Python dict 保持插入顺序后,是否意味着它变成“排序字典”?

回答要点: 不是;它记录插入顺序,不会按 key 大小自动排序。


训练 044 · Truthiness 与短路求值

↩ 返回知识卡 44

基础题
value = "" or "default"

value 是什么?

揭晓: "default"or 返回操作数本身,不强制转成 bool。

变体题

为什么 timeout = user_timeout or 30 可能错误处理合法值 0

揭晓: 0 是 falsy,会被误当成“没有提供”。若业务区分 0None,应显式判断。

真实开发 Bug

分页接口写 limit = request.limit or 100,用户明确传 0 想禁用返回却得到 100。

诊断: 把 falsy 当 missing;使用 sentinel/is None 按业务语义判断。

面试追问

对象 truthiness 的判定顺序是什么?

回答要点: 类型可通过 __bool__ 定义;若没有,可参考 __len__;否则一般视为真。


训练 047 · for 的真实基础:迭代协议

↩ 返回知识卡 47

基础题

for x in obj:iter()next()StopIteration 改写成等价心智模型。

揭晓:it = iter(obj),循环调用 next(it),捕获 StopIteration 结束。

变体题

为什么实现了 __getitem__(0...) 的老式对象有时也能被迭代?

揭晓: Python 还存在序列迭代回退协议;但现代自定义 iterable 应明确实现 __iter__

真实开发 Bug

自定义容器的 __iter__ 返回 self,但对象本身没有正确维护独立迭代状态,嵌套两层 for 相互干扰。

诊断: 可迭代容器通常每次 __iter__ 返回新的 iterator;只有 iterator 自身通常返回 self

面试追问

Iterable 与 Iterator 的最小协议分别是什么?

回答要点: iterable 能产生 iterator;iterator 实现 __next__ 并且 iter(iterator) is iterator


训练 052 · Iterable 与 Iterator

↩ 返回知识卡 52

基础题

为什么 list 可以重复遍历,而一个 iterator 常被耗尽?

揭晓: list 是 iterable,每次可产生新的 iterator;iterator 自己保存当前遍历状态。

变体题
it = iter([1, 2])
a = iter(it)

通常 a is it 为什么为真?

揭晓: iterator protocol 要求 iterator 的 __iter__ 返回自身。

真实开发 Bug

函数参数接收 records,先 sum(1 for _ in records) 统计数量,再 for 处理;传 generator 时第二遍为空。

诊断: 不应默认 iterable 可重复消费;需要单遍处理、物化成 list,或要求可重入 iterable。

面试追问

API 类型标注用 Iterable[T]Iterator[T] 分别暗示什么?

回答要点: Iterable 只承诺可获取 iterator;Iterator 暗示调用方拿到的是带消费状态的单个迭代器。


训练 054 · Generator 与 yield

↩ 返回知识卡 54

基础题

调用一个包含 yield 的 generator function 时,函数体会立刻跑到底吗?

揭晓: 不会;调用先返回 generator object,实际执行在迭代/next() 时推进,并在 yield 处挂起。

变体题

为什么 generator 能保存局部变量状态,却不等于“后台线程”?

揭晓: 它保存暂停的 frame/执行状态,但只有调用方推进它时才执行,没有自动并行调度。

真实开发 Bug

代码返回数据库查询 generator 后立即关闭连接;调用方稍后迭代才真正访问 cursor,于是报连接已关闭。

诊断: lazy evaluation 改变了资源生命周期;要在资源有效期内消费,或让 generator 自己管理上下文。

面试追问

Generator expression 相比 list comprehension 的核心 trade-off?

回答要点: 更低峰值内存和惰性消费,但单次、延迟执行、异常与资源生命周期也被推迟。


训练 057 · Class、Instance 与 Attribute

↩ 返回知识卡 57

基础题
class A:
    tags = []

a = A(); b = A()
a.tags.append("x")

b.tags 是什么?

揭晓: ['x'],两个实例都从类属性读取同一个 list。

变体题

执行 a.tags = [] 后,为什么 b.tags 仍指向原类属性?

揭晓: 给实例赋值会在 a.__dict__ 创建同名实例属性,遮蔽类属性,而不是修改类属性。

真实开发 Bug

模型类把 errors = [] 写成类属性,每个请求实例的错误相互串台。

诊断: 可变实例状态应在 __init__ / dataclass default_factory 中创建。

面试追问

实例属性查找为什么不只是查 obj.__dict__

回答要点: 还涉及 class、MRO、descriptor,以及 __getattribute__/__getattr__ 协议。


训练 058 · self、实例方法、classmethod、staticmethod

↩ 返回知识卡 58

基础题

obj.method(1) 为什么可粗略理解成 Cls.method(obj, 1)

揭晓: 函数作为 class attribute 是 non-data descriptor,访问时会创建 bound method,把实例绑定为第一个参数。

变体题

classmethod 第一个参数为什么是实际调用类而不是“定义它的那个类”固定值?

揭晓: descriptor 绑定的是调用上下文中的 class,因此继承时可获得子类。

真实开发 Bug

工厂方法硬编码 return Base(...),子类调用后仍返回 Base。

诊断: 如果希望多态构造,应使用 @classmethodreturn cls(...)

面试追问

什么时候 staticmethod 比模块级函数更合适?

回答要点: 行为概念上属于类命名空间、但不需要实例/类状态时;否则模块函数通常更简单。


训练 061 · MRO 与 super()

↩ 返回知识卡 61

基础题

多继承中 super() 是“调用父类”还是“沿 MRO 调用下一个实现”?

揭晓: 后者。

变体题

为什么 cooperative multiple inheritance 要求链上的方法签名和 super() 使用方式互相兼容?

揭晓: 每一层都需要把调用继续传递给 MRO 下一节点;某层截断或参数不兼容会破坏整个链。

真实开发 Bug

一个 mixin 的 __init__ 没调用 super().__init__(),导致 MRO 后面的类初始化逻辑完全没执行。

诊断: 在 cooperative hierarchy 中每层都应遵守协作式 super() 协议。

面试追问

为什么不能把 super() 简单翻译为“我的父类对象”?

回答要点: 它是绑定了当前类型和实例/类的代理,根据 MRO 找下一个实现,并不是某个固定父类实例。


训练 065 · Attribute Lookup、__getattr____getattribute__

↩ 返回知识卡 65

基础题

__getattribute____getattr__ 谁更早参与属性访问?

揭晓: 所有常规属性访问都先经过 __getattribute__;只有正常查找失败时才进入 __getattr__ fallback。

变体题

重写 __getattribute__ 时直接写 self.__dict__ 为什么容易无限递归?

揭晓: 访问 self.__dict__ 本身也会再次进入 __getattribute__;通常委托 object.__getattribute__(self, name)

真实开发 Bug

代理对象在 __getattr__ 中对任何缺失属性都返回 None,结果拼写错误静默通过。

诊断: fallback 太宽;未知属性应适当抛 AttributeError,否则破坏调试与 introspection。

面试追问

Descriptor 在典型 attribute lookup 优先级中的位置?

回答要点: data descriptor 通常高于 instance dict;non-data descriptor 通常低于 instance dict;再到 class/MRO 与 __getattr__ fallback。


训练 066 · Descriptor

↩ 返回知识卡 66

基础题

一个只实现 __get__ 的 descriptor 通常属于 data 还是 non-data descriptor?

揭晓: non-data descriptor;若实现 __set____delete__,通常成为 data descriptor。

变体题

为什么普通函数放到 class 上会自动产生 bound method?

揭晓: function object 实现 descriptor protocol,访问 obj.func__get__ 完成绑定。

真实开发 Bug

自定义 descriptor 把值存在 descriptor 实例自己的 self.value,结果所有宿主实例共享同一个值。

诊断: descriptor 对象本身通常也在 class 上共享;实例数据应按实例存储,例如写入实例 __dict__ 或外部弱引用映射。

面试追问

property 与 descriptor 是什么关系?

回答要点: property 本身就是标准库/内建提供的 descriptor,用 descriptor protocol 控制属性读取/写入/删除。


训练 073 · Context Manager 与 with

↩ 返回知识卡 73

基础题

with resource: 最重要的价值是“少写 close()”还是“把获取/释放绑定到控制流退出”?

揭晓: 后者;无论正常返回还是异常,都能进入退出逻辑。

变体题

__exit__ 返回 truthy 会发生什么?

揭晓: 可以表示异常已被处理,从而抑制异常继续传播;这必须非常谨慎。

真实开发 Bug

数据库 helper 的 __exit__ 无条件返回 True,SQL 异常被吞掉,接口却返回成功。

诊断: 不应无差别 suppress exception;通常清理后返回 falsey,让异常继续传播。

面试追问

Generator-based @contextmanager 如何映射到 enter/exit 语义?

回答要点: yield 前做 acquire,yield 的值对应进入值,yield 后/finally 做 release,异常会被注入 generator。


训练 079 · sys.modules 与 Import Cache

↩ 返回知识卡 79

基础题

同一进程中第二次 import mod 通常会重新完整执行模块顶层代码吗?

揭晓: 通常不会;先查 sys.modules 中已经加载的 module object。

变体题

为什么 importlib.reload(mod) 也不能简单理解为“恢复一个全新模块进程状态”?

揭晓: reload 会重新执行模块代码,但已有外部引用、对象实例、from-import 绑定等可能仍指向旧对象。

真实开发 Bug

单测修改模块级全局缓存后,后续测试重新 import 以为能恢复初始状态,结果缓存还在。

诊断: import cache 复用同一模块对象;测试需要显式 reset 状态、隔离进程或谨慎 reload。

面试追问

循环导入为什么常出现“partially initialized module”?

回答要点: 模块执行完成前就已注册进 sys.modules,另一模块可拿到这个尚未定义完全部名称的对象。


训练 080 · Circular Import

↩ 返回知识卡 80

基础题

A import B,B 又 from A import x,但 A 尚未执行到 x = ...。为什么失败?

揭晓: B 看到的是 A 的半初始化 module object,x 尚不存在。

变体题

把 import 移进函数为什么“可能修复”但不一定是最佳设计?

揭晓: 它推迟导入时机,可能绕开初始化环;但真实问题往往是模块依赖方向/职责耦合。

真实开发 Bug

models.py import services.pyservices.py 又 import models.py,随着功能增长不断出现循环。

诊断: 抽取共享协议/类型/常量到下层模块,重新设计依赖方向,而不是继续散布局部 import。

面试追问

import modulefrom module import name 在循环导入下哪个更容易受“名称尚未创建”影响?

回答要点: 后者需要导入当下立即解析具体名称,因此更直接暴露半初始化问题;前者可晚些通过 module 访问。


训练 082 · Type Hint 不改变动态类型本质

↩ 返回知识卡 82

基础题
def f(x: int) -> int:
    return x

print(f("hello"))

在没有额外 runtime validator 时会怎样?

揭晓: 正常返回字符串;annotation 本身通常不强制运行时类型。

变体题

既然运行时不强制,Type Hint 有什么实际价值?

揭晓: 静态检查、IDE、重构、文档、API 合约表达,以及供框架/工具选择性读取 metadata。

真实开发 Bug

团队以为 user_id: int 会自动拒绝 HTTP 字符串输入,实际运行到数据库层才出错。

诊断: typing 不是 validation;输入边界仍需解析与校验。

面试追问

Structural typing/Protocol 和继承式 nominal typing 的差别是什么?

回答要点: Protocol 可按对象具备的结构/能力静态匹配,不要求显式继承指定基类。


训练 106 · Concurrency 与 Parallelism

↩ 返回知识卡 106

基础题

单核 CPU 上两个任务交替推进,可以叫 concurrency 吗?可以叫真正同时 parallelism 吗?

揭晓: 可以并发;不一定并行。

变体题

异步 IO 为什么主要解决“等待期间让出执行权”,而不是让 CPU 密集计算变快?

揭晓: event loop 依赖协作切换;CPU 长计算不 await 就会持续占用线程。

真实开发 Bug

团队把图像压缩 CPU 任务改成 async def,吞吐没有提升反而卡住整个服务。

诊断: 仅加 async 不会把 CPU 工作变并行;考虑进程池、原生扩展或真正并行运行方式。

面试追问

选择 thread/process/asyncio 的第一判断维度有哪些?

回答要点: CPU-bound/IO-bound、共享内存需求、隔离需求、库是否阻塞、任务规模与调度开销。


训练 110 · Race Condition 与 Lock

↩ 返回知识卡 110

基础题

“检查余额足够 → 扣减余额”由多条操作组成,即使单条操作各自安全,整体为什么仍会竞态?

揭晓: check-then-act 不是原子事务;其他执行单元可在中间改变共享状态。

变体题

加了 Lock 是否就自动避免所有并发问题?

揭晓: 不会;锁粒度、锁顺序、覆盖的数据不一致、死锁、跨进程状态等仍需设计。

真实开发 Bug

两个线程都执行 if key not in cache: cache[key] = expensive(),昂贵任务被执行两次。

诊断: 复合操作存在 race;需要用锁保护整个检查/创建临界区或使用并发安全的 single-flight 设计。

面试追问

为什么 GIL 不等于你的 Python 业务逻辑“线程安全”?

回答要点: GIL 控制解释器层执行权,不保证多条 Python 操作构成的业务不变量原子,也会在 IO/扩展等处切换。


训练 113 · 传统 GIL 心智模型

↩ 返回知识卡 113

基础题

传统 GIL-enabled CPython 中,同一解释器内多个线程能否同时执行 Python bytecode?

揭晓: 通常一个时刻只有持有 GIL 的线程执行 Python bytecode。

变体题

为什么 I/O-bound 多线程仍可能明显提速?

揭晓: 阻塞 IO/很多 C API 可释放 GIL,其他线程可以在等待期间推进。

真实开发 Bug

CPU 密集纯 Python 任务从单线程改 8 线程,CPU 满但总体耗时没下降。

诊断: 传统 GIL 下线程不提供这种 Python bytecode CPU parallelism;考虑 process/free-threaded/释放 GIL 的原生实现。

面试追问

GIL 最容易被误解成哪三件事?

回答要点: “没有线程”“所有代码线程安全”“Python 永远不能并行”都不准确。


训练 114 · Free-threaded CPython 3.14

↩ 返回知识卡 114

基础题

Free-threaded build 的目标是什么?

揭晓: 允许 CPython 在不依赖传统全局 GIL 的构建中让多个线程并行执行 Python 代码,同时通过新的运行时机制维护对象安全。

变体题

“没有 GIL”是否意味着现有多线程代码无需锁?

揭晓: 恰恰相反;业务共享可变状态的 race 更需要显式同步。解释器保证内部结构安全,不等于你的不变量安全。

真实开发 Bug

迁移 free-threaded 后,原来“靠 GIL 碰巧没出问题”的共享 dict 读改写开始出现业务竞态。

诊断: 把隐式时序假设改成明确同步/消息传递;同时检查 C extension 的兼容性与是否会重新启用 GIL。

面试追问

为什么 free-threaded 不能简单说成“把 GIL 删除了就完事”?

回答要点: 需要替代引用计数、容器内部同步、内存分配等机制;性能、扩展兼容和对象生命周期行为也会变化。


训练 115 · Asyncio 核心模型

↩ 返回知识卡 115

基础题

Event loop 的核心职责是什么?

揭晓: 管理可运行 Task、等待 IO/定时事件,在 coroutine 明确让出执行权后调度其他任务。

变体题

创建很多 coroutine object 是否等于它们已经并发运行?

揭晓: 不等于;需要被 await 或包装成 Task/交给 event loop 调度。

真实开发 Bug

代码写 fetch(url) 得到 coroutine object 后没 await,程序结束时报 “coroutine was never awaited”。

诊断: 创建了 coroutine 但没有驱动执行;明确 await 或创建/管理 Task。

面试追问

Asyncio 的并发为什么叫 cooperative scheduling?

回答要点: coroutine 必须在 await 等点主动让出控制权;一个不让出的任务会占住 event-loop thread。


训练 117 · Task、TaskGroup 与结构化并发

↩ 返回知识卡 117

基础题

Coroutine 与 Task 的核心区别?

揭晓: coroutine 是可等待计算对象;Task 把 coroutine 注册到 event loop,代表被调度执行及其状态/结果。

变体题

为什么“创建 Task 后完全不保存/管理它”容易产生 orphan/background task 问题?

揭晓: 生命周期、异常、取消和 shutdown 边界不清楚;错误可能延迟暴露或任务被提前终止。

真实开发 Bug

请求里 create_task(write_audit()) 后立刻返回;进程 shutdown 时审计任务还没完成,也没人处理异常。

诊断: 若任务属于请求作用域,应使用结构化并发/TaskGroup 等明确等待与取消边界;真正后台任务需要独立生命周期管理。

面试追问

TaskGroup 相比随手 create_task() 的核心设计收益?

回答要点: 子任务生命周期被绑定到一个明确 scope,异常传播、等待和取消更可推理。


训练 119 · Blocking Code 会卡 Event Loop

↩ 返回知识卡 119

基础题
async def handler():
    import time
    time.sleep(5)

为什么这 5 秒会拖住同一 event loop 的其他任务?

揭晓: time.sleep 阻塞 event-loop 所在线程,没有 await 让出控制权。

变体题

把阻塞函数外面套一层 async def 会自动变成异步吗?

揭晓: 不会;内部依然同步占用线程。

真实开发 Bug

异步接口中直接调用同步 SDK,偶发 2 秒网络等待时所有请求延迟一起飙升。

诊断: 换原生 async SDK,或把合适的阻塞 IO 移到 asyncio.to_thread()/线程池,并评估线程安全。

面试追问

to_thread() 适合解决 CPU-bound 吗?

回答要点: 主要用于不会释放 event loop 的阻塞同步 IO;传统 GIL 下纯 Python CPU-bound 不会因此获得理想并行。


训练 124 · Reference Counting

↩ 返回知识卡 124

基础题

默认 GIL-enabled CPython 中,对象引用计数降为零通常意味着什么?

揭晓: 通常可以立即进入对象销毁/释放流程;但这是 CPython 实现模型,不是 Python 语言保证。

变体题

为什么不能把这条规则原封不动套到 free-threaded CPython 3.14?

揭晓: free-threaded 引入 biased/deferred 等引用计数机制和 immortal object 等优化,对象最终回收时机可不同。

真实开发 Bug

代码依赖 del obj 后“马上执行 finalizer 释放外部资源”,换实现/运行模式后行为变得不可靠。

诊断: 资源释放不应依赖 GC/refcount 时机;用 context manager/显式 close。

面试追问

引用计数最大的优点和经典缺陷?

回答要点: 生命周期通常及时且直观;循环引用不能靠计数归零自行解决。


训练 125 · 循环引用与 Cyclic GC

↩ 返回知识卡 125

基础题

两个对象只互相引用、没有外部可达引用时,为什么 refcount 可能都不为零?

揭晓: 它们彼此贡献引用,因此需要 cyclic GC 判断这组对象从根不可达。

变体题

“Python 有 GC,所以引用计数不重要”为什么不对?

揭晓: CPython 常规对象生命周期主要仍高度依赖引用计数;cyclic GC 主要补充处理可参与循环的容器对象图。

真实开发 Bug

长期服务中大量对象互相注册回调形成环,虽最终可 GC,但高峰内存持续很久。

诊断: 可考虑打断无必要强引用、使用 weakref、显式注销,不能只说“GC 最后会收”。

面试追问

CPython 3.14 的 GC 分代模型和旧教程相比为什么需要核对版本?

回答要点: 3.14 调整了 GC generation 结构/行为;涉及代数、阈值和调优时必须以目标版本官方文档为准。


训练 144 · subprocess

↩ 返回知识卡 144

基础题

运行固定命令时为什么通常优先:

subprocess.run(["git", "status"], check=True)

而不是拼 shell 字符串?

揭晓: argv 边界清楚,少一层 shell 解析,也更容易避免 injection。

变体题

check=True 解决的是 timeout 吗?

揭晓: 不是;它在子进程非零 exit code 时抛异常。超时需单独 timeout=

真实开发 Bug

服务调用 CLI,不读 stderr 也不检查 returncode,只根据 stdout 是否为空判断成功,导致错误被当成“无结果”。

诊断: 明确 exit code、stdout、stderr、timeout,并按协议解释结果。

面试追问

什么时候确实需要 shell=True?使用时应增加哪些边界?

回答要点: 需要 shell syntax/管道/内建命令时;输入应完全受控或严格设计,不把不可信数据拼进命令。


训练 148 · Timeout、Retry 与 Idempotency

↩ 返回知识卡 148

基础题

为什么网络调用“有 retry 却没有 timeout”仍然可能永久卡住?

揭晓: 单次尝试没有失败边界,重试逻辑根本得不到执行机会。

变体题

HTTP POST 失败后是否一定可以安全重试?

揭晓: 不一定;如果服务端已处理但响应丢失,重试可能重复产生副作用。需考虑幂等语义/幂等键。

真实开发 Bug

支付请求客户端 timeout 后自动重试 3 次,最终用户被扣款两次。

诊断: timeout 不代表远端没执行;支付写操作需要 idempotency key、服务端去重和明确重试策略。

面试追问

设计 retry policy 至少考虑哪些维度?

回答要点: 哪些错误可重试、幂等性、最大次数/总时限、指数退避与 jitter、上下游容量及可观测性。


训练 150 · Transaction

↩ 返回知识卡 150

基础题

三个相关 UPDATE 中第二个成功、第三个失败。transaction 的目标是什么?

揭晓: 把这组操作作为原子业务单元;失败时 rollback,避免只提交一半。

变体题

transaction 是否等于“拿一个全局锁,让其他事务都不能运行”?

揭晓: 不是;并发与隔离由数据库隔离级别/MVCC/锁机制等共同决定。

真实开发 Bug

函数内部先 commit(),然后再写审计表;审计失败后 rollback 已无法撤回前面的业务更新。

诊断: transaction boundary 切错;属于同一原子业务约束的写操作应放在同一事务边界内。

面试追问

应用层 transaction 和 connection pool 分别解决什么问题?

回答要点: transaction 管原子性/一致性边界;pool 管连接建立成本与复用,两者不是替代关系。


训练 156 · Fixture

↩ 返回知识卡 156

基础题

pytest fixture 最核心的价值是“少写几行 setup”吗?

揭晓: 更重要的是声明测试资源/依赖及其生命周期,并可组合 setup/teardown。

变体题

把可变 fixture 提升到 session scope 为什么可能制造测试相互污染?

揭晓: 多个测试共享同一对象状态;scope 越大,隔离责任越重。

真实开发 Bug

一个 session-scoped client fixture 在测试中修改默认 headers,后面的无关测试突然携带认证头。

诊断: 共享可变状态;降低 scope、每次重建,或在 teardown/测试前明确 reset。

面试追问

Fixture 与 helper function 的本质差异?

回答要点: fixture 进入测试框架的依赖解析、scope、teardown 与参数化模型;helper 只是普通函数调用。


训练 158 · Dummy、Stub、Spy、Mock、Fake

↩ 返回知识卡 158

基础题

只需要返回固定 API 响应,不关心调用次数,最接近 Stub 还是 Mock?

揭晓: Stub。

变体题

“Mock”为什么不应该成为所有 test double 的统称?

揭晓: 不同 double 关注点不同:Stub 提供结果、Spy 记录、Mock 验证交互、Fake 提供简化可运行实现等。

真实开发 Bug

单测把内部每个私有函数都 mock 掉,重构内部调用顺序后业务行为没变却几十个测试失败。

诊断: 测试过度绑定 implementation detail;应围绕可观察行为和真正的外部边界替换依赖。

面试追问

什么时候 Fake 比 Mock 更有价值?

回答要点: 当需要一个轻量但行为真实的替代实现(如 in-memory repository)来测试更完整协作,而不是只验证调用。


训练 159 · Patch 的位置

↩ 返回知识卡 159

基础题
# service.py
from client import send

def work():
    return send()

测试 work() 时通常 patch client.send 还是 service.send

揭晓: service.send,因为被测代码在自己的 namespace 查找这个绑定。

变体题

为什么 patch 原始定义位置有时“看起来成功”,有时完全没效果?

揭晓: 取决于被测模块持有的是动态 module attribute lookup 还是已经复制绑定的 from ... import name

真实开发 Bug

测试 patch requests.get,但模块顶层早已 from requests import get;测试仍发真实网络请求。

诊断: patch where looked up,即被测模块中的 get 名字。

面试追问

Patch 位置问题和 Python 名称绑定模型有什么直接关系?

回答要点: import 会在目标 namespace 建立绑定;函数运行时按该 namespace 查名称,不会自动回到最初定义模块。


训练 166 · Big O 与 Python 容器复杂度

↩ 返回知识卡 166

基础题

对 10 万元素做 10 万次 membership,为什么 set 往往比 list 合适?

揭晓: list membership 平均线性 O(n),set 平均 O(1) 哈希查找;一次建 set 的 O(n) 可被大量查询摊薄。

变体题

O(1) 是否意味着“永远只花 1 纳秒”?

揭晓: Big O 描述增长趋势,不给出常数,也不排除哈希冲突、缓存局部性、对象 hash 成本等现实因素。

真实开发 Bug

接口为每条订单都用 if user_id in user_list 在线性 list 查 5 万用户,总体变成隐藏 O(n²)。

诊断: 预构建 set(user_ids) 或 dict index,把重复查找变成平均 O(1)。

面试追问

复杂度优化和 micro benchmark 谁应先?

回答要点: 先确认算法/数据结构数量级,再用 profiling/benchmark 验证热点和实际常数。


训练 169 · Cache

↩ 返回知识卡 169

基础题

缓存为什么不仅是“加一个 dict”?至少还要考虑哪两个问题?

揭晓: key 设计、失效/一致性、容量/淘汰、并发、命中率、错误缓存等。

变体题

functools.lru_cache 用在有副作用函数上有什么风险?

揭晓: 后续相同参数可能直接返回旧结果而跳过副作用;缓存要求函数语义适合按输入复用结果。

真实开发 Bug

权限查询缓存 1 小时,用户被撤权后仍能访问。

诊断: 缓存失效策略不满足安全一致性要求;缩短 TTL、事件驱动失效,或对高风险决策避免过强缓存。

面试追问

Cache stampede 是什么?

回答要点: 热 key 同时过期,大量请求并发回源;可用 single-flight、随机 TTL、提前刷新等控制。


训练 170 · N+1、批处理与 IO 性能

↩ 返回知识卡 170

基础题

查询 100 个订单后,再为每个订单单独查一次用户,一共大约多少次数据库 round trip?

揭晓: 1 + 100 = 101 次,这就是典型 N+1 形态。

变体题

把 101 次查询改成“并发发 101 次”是否等价于解决 N+1?

揭晓: 不等价;可能降低单请求墙钟时间,却放大数据库连接/负载。应优先减少 round trip。

真实开发 Bug

ORM 页面本地测试很快,线上 1000 条结果导致 1001 次 SQL,连接池被打满。

诊断: eager loading/join/batch IN query 等减少访问次数,并用 tracing/query count 监测。

面试追问

为什么 IO 性能常应先优化“次数”而不是 Python 循环本身?

回答要点: 网络/数据库 round trip 延迟通常远大于本地微秒级操作,减少边界穿越收益更大。


训练 174 · pyproject.toml

↩ 返回知识卡 174

基础题

pyproject.toml 在现代 Python 项目中可承载哪些类别的信息?

揭晓: build system、project metadata,以及很多工具的统一配置;具体字段受相应规范/工具约定约束。

变体题

存在 pyproject.toml 是否意味着项目一定使用某个固定构建工具?

揭晓: 不是;它是标准配置入口,可声明不同 build backend,也可只承载工具配置。

真实开发 Bug

项目只在开发者电脑可安装,CI 构建报缺 build backend,原因是本地环境“碰巧装过”依赖却没在 build-system 中声明。

诊断: 构建环境必须可从项目元数据独立复现,不依赖全局环境偶然状态。

面试追问

[build-system][project] 解决的问题分别是什么?

回答要点: 前者描述构建需要什么 backend/requirements;后者描述 distribution 的标准项目元数据。


训练 175 · Wheel 与 Source Distribution

↩ 返回知识卡 175

基础题

Wheel 和 sdist 最核心区别是什么?

揭晓: wheel 是构建好的 distribution 格式,安装通常无需再执行项目构建;sdist 是源码分发,需要目标环境构建。

变体题

为什么含原生扩展的 wheel 常带平台/ABI 标签,而纯 Python wheel 更通用?

揭晓: 编译产物依赖 OS/架构/Python ABI;纯 Python 文件没有这种二进制兼容限制。

真实开发 Bug

生产环境离线安装包只有 sdist,却没有编译器和系统头文件,部署失败。

诊断: 部署前构建并验证目标平台可用 wheel,或准备完整构建工具链。

面试追问

pip install 一个 sdist 时“安装”为什么可能实际包含构建步骤?

回答要点: installer 会调用声明的 build backend 构建 wheel/产物,再安装到环境。


训练 181 · eval()exec()

↩ 返回知识卡 181

基础题

把用户输入传给 eval() 的核心风险是什么?

揭晓: 把“不可信数据”提升成“可执行 Python 代码”。

变体题

只把 globals={"__builtins__": {}} 传给 eval 是否就得到可靠安全沙箱?

揭晓: 不应这样假设;Python 对象模型可提供复杂逃逸路径,eval 不是为安全沙箱设计的。

真实开发 Bug

配置系统允许管理员输入公式并直接 eval(formula),后来权限边界变化,低权限用户也能编辑公式。

诊断: 不应依赖“现在只有可信人会输入”;改为解析受限表达式 DSL/AST 白名单或明确支持的运算集合。

面试追问

什么时候 ast.literal_evaleval 合适?它又不解决什么?

回答要点: 只需解析 Python literal 结构时更安全;它不是通用表达式执行器,也仍需考虑输入大小/资源耗尽。


训练 182 · Command Injection

↩ 返回知识卡 182

基础题

为什么:

subprocess.run(["grep", user_text, path])

通常比 shell=True 拼接字符串更安全?

揭晓: 参数以 argv 边界传给程序,不再由 shell 把用户内容解释为 ;, &&, $() 等控制语法。

变体题

使用 argv list 是否意味着所有输入风险都消失?

揭晓: 没有;目标程序自己的 option injection、路径、资源、语义约束仍需验证,例如用户值以 - 开头可能被解释成选项。

真实开发 Bug

图片转换服务执行 convert "{filename}" out.png,攻击者上传文件名 x.jpg; curl ...

诊断: shell command injection;不要拼 shell,使用 argv、受控路径和目标工具自身的安全选项。

面试追问

Command injection 与 SQL injection 有什么共同抽象?

回答要点: 都把不可信数据混入了指令语法;解决核心是让“数据”和“代码/命令结构”保持分离。


训练 185 · random vs secrets

↩ 返回知识卡 185

基础题

生成密码重置 token 应用 random 还是 secrets

揭晓: secrets

变体题

random.seed() 一个“看起来很随机”的当前时间,是否适合安全 token?

揭晓: 不适合;Mersenne Twister 面向模拟/统计,输出可预测,不提供密码学安全保证。

真实开发 Bug

邀请码使用 random.randint(100000, 999999),攻击者可大量枚举且可能推断序列。

诊断: 如果邀请码承担认证/授权秘密,应提高熵并使用 secrets,同时结合速率限制/过期等机制。

面试追问

secrets 解决了 token 系统的全部安全问题吗?

回答要点: 只解决安全随机来源;还需长度/熵、存储、过期、单次使用、比较、防暴力破解等设计。


训练 189 · EAFP 与 LBYL

↩ 返回知识卡 189

基础题

访问 dict 可选 key 时,try/except KeyError 与先 if key in d 各体现什么风格?

揭晓: 前者偏 EAFP;后者偏 LBYL。

变体题

EAFP 是否意味着 except Exception: pass

揭晓: 完全不是;EAFP 应捕获预期且具体的异常,并保持失败可解释。

真实开发 Bug

if path.exists(): path.open(),两个操作之间文件被删除,仍报错。

诊断: 典型 TOCTOU;在不可原子保证的环境里,直接尝试 open 并处理具体异常通常更可靠。

面试追问

什么时候 LBYL 更合理?

回答要点: 检查本身就是业务规则、能避免昂贵/不可逆副作用,或 API 提供稳定且无竞争窗口的预检时。


训练 190 · Pure Function 与 Side Effect

↩ 返回知识卡 190

基础题

“相同输入总得到相同输出且不修改外部状态”描述的是什么倾向?

揭晓: Pure function。

变体题

读取当前时间的函数没有写文件,为什么仍不算纯函数?

揭晓: 结果依赖隐式外部状态(时间),相同显式输入不保证相同输出。

真实开发 Bug

calculate_price(order) 内部偷偷读取全局促销配置和当前时间,单测在不同日期随机失败。

诊断: 把隐式依赖显式参数化(规则、clock),纯计算与 IO/状态读取分层。

面试追问

为什么纯函数常更容易缓存、并行和测试?

回答要点: 输入输出关系稳定,缺少共享副作用,结果可重现,依赖边界明确。


训练 192 · Composition vs Inheritance

↩ 返回知识卡 192

基础题

“Car has an Engine” 更自然倾向 composition 还是 inheritance?

揭晓: composition。

变体题

为了复用 5 行代码就继承一个基类,为什么可能是坏设计?

揭晓: inheritance 建立的是更强的 is-a、替换性和生命周期耦合,不只是代码复用工具。

真实开发 Bug

业务类继承某 SDK Client 只为复用 request(),后来 SDK 增加初始化约束和方法名冲突,业务类被迫跟着变化。

诊断: 依赖能力而非“成为 SDK Client”;把 client 作为组合依赖注入更稳。

面试追问

什么时候 inheritance 反而是合适的?

回答要点: 存在稳定的 is-a 关系、基类明确设计为扩展点、子类能遵守替换契约,并且 MRO/生命周期可控。