📖 配套主手册:《Python 碎片化复习手册 · CPython 3.14》
🎯 训练规模:55 个核心知识训练组 · 覆盖变体 / Bug / 面试追问(另含 55 道高频速查题与专题路径收口题)
💡 使用建议:自测题答案默认折叠,建议先在脑海或草稿中尝试作答,再点击展开核对。答错或不稳时可一键回查主手册对应知识卡。
使用规则
- 先在主手册把对应卡至少学到 M1。
- 变体题训练 M2:改变条件后预测行为。
- 真实开发 Bug训练 M3:从症状定位机制并给修复方向。
- 面试追问训练 M3~M4:解释边界、反例和工程权衡。
- 答错时不要直接背答案;先根据调度 metadata 的
rollback回退前置卡。
专题路径收口题
专题路径读完后,不要立刻换下一条。先合上正文,回答下面对应的一题。这里故意不直接给完整答案;答不稳时按“参考卡”回查,而不是从头重读。
| 路径 | 收口题 | 参考卡 |
|---|---|---|
| 对象模型 | a = [[1]]; b = a.copy(); b[0].append(2) 后,画出 a、b、外层 list、内层 list 的对象关系图。 |
6、9 |
| 函数参数 | 一个函数先 items.append(1),再 items = []。调用方最后观察到什么?为什么两个语句影响不同? |
13 |
| 作用域 / 闭包 | 解释为什么 lambda: i 在循环结束后通常都得到最终 i,以及 lambda i=i: i 为什么能修复。 |
22、25 |
| 装饰器 | 不使用 @ 语法,把一个带参数装饰器的绑定过程完整写出来,并指出闭包保存了哪些状态。 |
24、26、27 |
| 容器 / 复杂度 | 你需要对 10 万条记录按 id 做 10 万次 membership/query。说明何时应从 list 转成 dict/set,以及构建成本为什么值得。 | 34、38、166 |
| 迭代 / 生成器 | 为什么 list(generator) 后再次 list(generator) 常得到空列表?如果需要两次完整遍历,设计上有哪些选择? |
52、54 |
| OOP / Data Model | 解释 obj.method 为什么能自动绑定 self,并说明实例属性、data descriptor、non-data descriptor 的查找优先级。 |
58、65、66 |
| Import | A import B,B 又从 A 导入一个尚未定义的名字。用 sys.modules 和模块初始化顺序解释失败。 |
79、80 |
| Typing | 为什么 def f(x: int): ... 仍可在运行时收到字符串?那静态类型检查到底替你解决什么问题? |
82 |
| Thread / GIL | 一个“先读余额、判断、再扣款”的多步操作,在传统 GIL CPython 下为什么仍需要同步?free-threaded 下又有什么变化? | 110、113、114 |
| Asyncio | 一个 async handler 内调用 time.sleep(5)。从 event loop 角度解释其他请求为什么也会被拖住,并给出两个修复方向。 |
115、119 |
| GC / 生命周期 | 画出两个对象互相引用、外部名字都被删除后的对象图,并解释引用计数和 cyclic GC 各负责什么。 | 124、125 |
| subprocess / 安全 | 为什么 shell=True + 用户字符串是危险组合?仅改成 argv list 后还剩哪些输入安全问题? |
144、182 |
| 数据库 | 一个请求更新三张表,第二张成功后第三张失败。说明 transaction 边界、rollback 和 connection pool 分别解决什么问题。 | 150、151 |
| 测试 / Mock | 被测模块 from client import send 后调用 send()。你 patch 哪个名字?再说明 Stub 与 Mock 的区别。 |
158、159 |
| 性能 | 一个接口慢 800ms,profiling 发现 Python 计算只占 20ms,却执行了 101 次 SQL。你先优化哪里?为什么? | 167、170 |
| Packaging | 从源码仓库到 pip install,用 pyproject.toml → build backend → wheel/sdist → installer 串一次完整链路。 |
174、175 |
| 安全 | 分别判断“不可信字符串进入 eval”“进入 shell”“作为随机 token 来源”三种场景的第一安全边界。 | 181、182、185 |
30 秒随机复习
这一节只作为随机入口。答案默认折叠/跳回主卡,避免滚动时提前看到。
下面这些问题非常适合日常随机抽查。建议按“先答 → 标记置信度 → 再核对”的方式使用:
2 = 能解释原因,并能预测一个变体
1 = 只记得结论,原因不稳
0 = 想不起来 / 判断错
这里的 0/1/2 只是本次作答结果,不是掌握度;长期状态仍只使用 U/M0~M4。
优先复习 0 分卡,其次 1 分卡;连续两次得到 2 分后再拉长复习间隔。能不用运行代码直接解释,并能处理一个小变体,才说明心智模型在变稳。
a = b为什么通常不是复制对象?is与==的区别是什么?- 为什么 tuple 里仍然可以“看到内部 list 被修改”?
- 为什么 mutable default argument 会跨调用共享?
- 参数传递为什么不能简单说“list 引用传递、int 值传递”?
global与nonlocal分别改变哪层绑定?- closure 为什么能在 outer 返回后继续访问状态?
- late binding 到底绑定的是什么?
@decorator的等价展开是什么?- 为什么
functools.wraps值得用? bytes与str的边界是什么?append()与extend()差别是什么?- dict 为什么平均查找接近 O(1)?
- dict 为什么保持插入顺序但仍然是哈希表?
and/or为什么不一定返回 bool?- loop
else什么时候执行? - iterable 与 iterator 有什么区别?
- generator 为什么通常只能消费一次?
yield保存了什么执行状态?self为什么会被自动传入实例方法?super()为什么不是简单“父类对象”?- descriptor 与 property、method binding 有什么关系?
finally为什么适合清理资源?- 为什么不应吞掉
Exception? - import 为什么会缓存 module object?
- circular import 为什么经常是架构信号?
- Type Hint 为什么不自动做 runtime validation?
Any和object有什么区别?- Python 3.14 的注解求值发生了什么变化?
- 文本文件为什么应明确 encoding?
pickle.loads()为什么不能处理不可信数据?- thread、process、coroutine 分别在哪个层次?
- GIL 为什么不等于线程安全?
- Python 3.14 free-threaded 为什么不能直接理解成“所有 Python 都无 GIL”?
- async function 调用为什么得到 coroutine object?
- blocking call 为什么会卡 event loop?
- TaskGroup 比裸
create_task生命周期管理好在哪里? - 引用计数为什么解决不了循环引用?
- 对象释放后 RSS 为什么不一定下降?
dis能回答什么、不能保证什么?- subprocess 为什么优先 argument list?
- 网络请求为什么一定要考虑 timeout?
- retry 为什么必须考虑幂等性?
- transaction 边界为什么是业务设计问题?
- fixture 与 mock 分别解决什么问题?
- patch 为什么要 patch 使用位置?
- coverage 为什么不能等价测试质量?
- profiler 与 benchmark 的问题分别是什么?
- cache 为什么会延长对象生命周期?
- virtual environment 为什么不是安全 sandbox?
- wheel 和 sdist 有什么区别?
- formatter、linter、type checker 为什么不是同一种工具?
secrets为什么比random更适合 token?- EAFP 与 LBYL 什么时候各自更清楚?
- composition 为什么经常比 inheritance 更容易演进?
自测答案与主知识卡索引
展开 55 道简短答案
建议先口头回答,再展开这里核对。答案刻意保持短,只用于校准心智模型。
- 赋值通常只新增/改变名字到对象的绑定,不自动复制对象;复制需要显式 copy/deepcopy。 → 卡 6
==关注相等性语义,is关注对象 identity;普通值比较通常用==。 → 卡 8- tuple 不允许替换自己保存的引用,但它引用的可变 list 仍可被原地修改。 → 卡 7
- 默认参数表达式在函数定义时求值;默认 list/dict 会被多次无参调用复用。 → 卡 16
- 参数模型对类型是一致的:形参名绑定到传入对象;差异来自对象可变性以及函数执行 mutate 还是 rebind。 → 卡 13
global指向模块全局绑定;nonlocal指向最近的 enclosing function scope 绑定。 → 卡 23- closure 保存了对 free variable 所在 cell/环境的引用,因此 outer frame 结束后相关对象仍可存活。 → 卡 24
- late binding 绑定的是变量/名称所在 cell,而不是在创建 lambda 时自动冻结当时的值。 → 卡 25
@decorator可理解为定义函数后执行func = decorator(func)。 → 卡 26wraps复制/维护__name__、__doc__、__wrapped__等元数据,利于调试、内省和工具链。 → 卡 26str表示 Unicode 文本;bytes表示原始字节。文本进出二进制边界需要显式 encode/decode。 → 卡 31append(x)把 x 作为一个元素加入;extend(iterable)逐个加入 iterable 的元素。 → 卡 34- dict 基于哈希表,平均情况下 hash 定位让查找/插入接近 O(1),最坏情况不能机械保证。 → 卡 38
- “保持插入顺序”描述迭代顺序;底层仍可使用哈希表维护 key 定位,两者不矛盾。 → 卡 38
and/or返回参与短路求值的操作数之一,而不是强制转换成 bool。 → 卡 44- 循环正常耗尽且没有被
break终止时执行else。 → 卡 49 - iterable 能产生 iterator;iterator 还保存迭代状态并实现
__next__()。 → 卡 52 - generator object 本身就是 iterator,消费会推进内部状态;耗尽后不会自动重新开始。 → 卡 54
yield暂停 frame,保留指令位置、局部变量和执行上下文,以便下次恢复。 → 卡 54- 函数对象是 descriptor;通过实例访问时
__get__()产生 bound method,把实例预绑定为第一个参数。 → 卡 58 super()根据 MRO 从当前位置继续查找,不是“直接拿到父类对象”。 → 卡 61- descriptor 控制属性访问;property 是 data descriptor,普通方法绑定也依赖函数 descriptor。 → 卡 66
finally无论正常返回还是异常传播通常都会运行,适合释放资源和恢复状态。 → 卡 73- 过宽捕获后不处理会隐藏真实失败、破坏可观测性;只捕获能正确处理的异常。 → 卡 69
- 成功 import 后 module object 会进入
sys.modules,后续 import 通常复用它,避免重复执行模块顶层代码。 → 卡 79 - 它常暴露模块初始化顺序互相依赖和职责耦合;可以通过重新划分依赖方向或延迟导入解决。 → 卡 80
- Type Hint 主要给静态 checker/IDE/人阅读,Python 运行时默认不会按注解自动拒绝不匹配参数。 → 卡 82
object表示“某个具体但未知的普通对象”,使用前需缩窄;Any会让静态检查器放弃很多检查。 → 卡 84- 3.14 默认从 eager 变为 deferred evaluation;但
from __future__ import annotations仍是字符串化语义。 → 卡 91 - 文本必须经编码映射到字节;不显式 encoding 会依赖环境默认值,导致跨机器/区域不稳定。 → 卡 92
- pickle 反序列化可以触发对象构造/代码路径,不是只读取“纯数据”;不可信输入可导致代码执行风险。 → 卡 96
- process 是 OS 资源/地址空间单位;thread 是进程内执行流;coroutine 是由语言/事件循环协作调度的可暂停计算。 → 卡 140
- GIL 只限制默认 CPython 同一时刻执行 Python 代码的线程数量,不保护你的多步骤业务不变量,也不消除 race condition。 → 卡 113
- free-threaded 是可选构建;GIL 还可能被显式启用或因不兼容 C extension 在运行时重新启用。 → 卡 114
- 调用
async def不立即跑完整函数体,而创建 coroutine object;需要 await/Task 驱动它。 → 卡 116 - event loop 依赖任务主动让出执行权;同步阻塞调用不 await,会占住 loop 线程。 → 卡 119
- TaskGroup 把子任务生命周期绑定在结构化作用域内,并统一处理等待、失败和取消传播。 → 卡 117
- 循环中的对象彼此引用,即使外部不可达,单纯 refcount 也可能都不为 0,需要 cyclic GC 识别不可达对象图。 → 卡 125
- 对象释放只说明对象生命周期结束;allocator、分片、QSBR/mimalloc purge 或 OS 策略都可能让 RSS 暂时不降。 → 卡 128
dis能观察当前 CPython 字节码/指令形态;它不能保证跨版本/跨实现稳定,也不能单独证明高层语义性能。 → 卡 132- argument list 避免 shell 再解析字符串;但还要防目标 CLI 自身的 option injection。 → 卡 144
- 网络可能无限慢、半连接或对端不响应;没有 timeout 就可能无限占用线程/任务/连接。 → 卡 148
- 重试会重复执行请求;非幂等操作可能重复扣款/创建资源,因此要设计 idempotency key 或幂等语义。 → 卡 148
- 事务决定哪些业务状态必须原子提交/回滚,边界过大或过小都会影响一致性、锁和并发。 → 卡 150
- fixture 提供可复用测试环境/依赖;mock/test double 替代边界依赖并观察交互,目标不同。 → 卡 156
- patch 应替换“被测代码实际查找该名字的位置”,而不是机械 patch 对象最初定义的位置。 → 卡 159
- coverage 只说明哪些代码被执行,不证明断言充分、边界完整或行为正确。 → 卡 161
- benchmark 回答“这一段/方案多快”;profiler 回答“时间/资源主要花在哪里”。 → 卡 167
- cache 本质保留对象引用以便复用,所以会延长被缓存 key/value 的生命周期并增加内存占用。 → 卡 169
- venv 隔离 Python package 依赖,不隔离 OS 权限、文件、网络、进程等安全边界。 → 卡 172
- wheel 是预构建 distribution 格式;sdist 是源代码分发,需要目标环境执行构建流程。 → 卡 175
- formatter 统一格式;linter 找风格/潜在错误;type checker 做静态类型分析,三者问题域不同。 → 卡 179
random面向模拟/一般随机;secrets使用适合安全 token/凭证场景的随机源和 API。 → 卡 185- EAFP 在异常确实代表少见失败且竞争窗口存在时清楚;LBYL 在检查本身便宜、语义明确且不是竞态检查时可读。 → 卡 189
- 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 | global 与 nonlocal |
原卡 |
| 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
基础题
a = [1, 2]
b = [1, 2]
c = a
不运行代码,判断 a == b、a is b、a is c。
揭晓: True / False / True。== 讨论值;is 讨论对象身份。
变体题
为什么不能根据 id(a) 的某个具体数字去推断对象的物理内存地址?
揭晓: id() 保证的是对象生命周期内的 identity 值;CPython 常把它实现得像地址,但这是实现细节,不是 Python 语言承诺。
真实开发 Bug
缓存层用 payload is cached_payload 判断“数据有没有变化”,结果相同内容但重新解析出的 dict 总被判为变化。修复应改成什么?
诊断: 业务想比较 value,应使用合适的 == 或业务字段/哈希,而不是 identity。
面试追问
“Python 一切皆对象”对函数、类、模块意味着什么?
回答要点: 它们都可以被绑定名称、传参、返回、存入容器;这直接支撑高阶函数、装饰器、反射与元编程。
训练 006 · 引用与对象共享
基础题
a = {"items": []}
b = a
b["items"].append(1)
a 是什么?
揭晓: {"items": [1]},因为 a、b 引用同一个 dict,内部又引用同一个 list。
变体题
a = []
b = [a, a]
b[0].append(1)
b 的两个元素是什么关系?
揭晓: 两个槽位都引用同一个 list,所以 b == [[1], [1]]。
真实开发 Bug
配置模板 dict 被多个请求复用,请求 A 在嵌套 headers 中添加字段后,请求 B 也看到了。根因是什么?
诊断: 共享了可变对象图;需要明确每请求对象边界,必要时重建或复制对应层级。
面试追问
“容器装的是对象还是对象引用?”如何用这个模型解释嵌套对象共享?
回答要点: Python 容器保存的是对对象的引用;外层对象独立不代表内部对象也独立。
训练 007 · Mutable 与 Immutable
基础题
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 与 ==
基础题
什么时候应优先写 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 · 浅拷贝与深拷贝
基础题
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 参数传递模型
基础题
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 · 完整参数语法
基础题
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 · 默认参数在定义时求值
基础题
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 名称查找
基础题
函数内部读取 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 · global 与 nonlocal
基础题
global x 和 nonlocal x 分别改变哪个作用域中的绑定?
揭晓: global 指向模块全局绑定;nonlocal 指向最近的 enclosing function scope 绑定。
变体题
为什么 nonlocal 不能创建一个此前不存在的 enclosing 名称?
揭晓: 它要求在外层函数作用域中已经存在可绑定名称,否则无法解析目标 cell。
真实开发 Bug
一个闭包计数器忘写 nonlocal count,执行 count += 1 时触发 UnboundLocalError。
诊断: 赋值让 count 被认作当前局部;应显式 nonlocal count 或改成更清晰的状态对象。
面试追问
什么时候应该避免 global/nonlocal,即使语法允许?
回答要点: 当共享可变状态让依赖隐蔽、测试困难或并发风险升高时,优先显式传递状态或封装对象。
训练 024 · 闭包 Closure
基础题
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
基础题
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 · 装饰器的本质
基础题
@trace
def f():
pass
不用 @ 写出等价绑定。
揭晓: 先创建原函数,再执行 f = trace(f)。
变体题
为什么装饰器通常在模块 import/函数定义阶段执行,而 wrapper 在函数调用时执行?
揭晓: 装饰表达式参与函数对象创建后的重新绑定;返回的 wrapper 才是以后真正被调用的对象。
真实开发 Bug
自定义装饰器没用 functools.wraps,框架拿到的函数名、docstring、annotation 都变成 wrapper 的。
诊断: 元数据丢失;用 @wraps(func),必要时还要考虑签名保持。
面试追问
装饰器和显式函数组合相比,最大的设计风险是什么?
回答要点: 行为被隐式包裹,调用链/异常/性能/状态可能不透明;应控制层数并保持单一职责。
训练 031 · str、Unicode、编码与解码
基础题
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:动态数组心智模型
基础题
为什么 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:哈希表与顺序
基础题
为什么 dict 查找平均接近 O(1)?
揭晓: 对 key 计算 hash 后定位哈希表槽位,通常无需线性扫描全部键。
变体题
两个对象 a == b 且都可哈希时,hash(a) 与 hash(b) 应满足什么关系?
揭晓: 必须相等;否则会破坏哈希容器的等价性约束。
真实开发 Bug
自定义 key 对象实现了基于可变字段的 __hash__,插入 dict 后字段改变,随后“明明在 dict 里却查不到”。
诊断: hash/equality 参与字段必须在作为 key 期间保持稳定。
面试追问
Python dict 保持插入顺序后,是否意味着它变成“排序字典”?
回答要点: 不是;它记录插入顺序,不会按 key 大小自动排序。
训练 044 · Truthiness 与短路求值
基础题
value = "" or "default"
value 是什么?
揭晓: "default"。or 返回操作数本身,不强制转成 bool。
变体题
为什么 timeout = user_timeout or 30 可能错误处理合法值 0?
揭晓: 0 是 falsy,会被误当成“没有提供”。若业务区分 0 与 None,应显式判断。
真实开发 Bug
分页接口写 limit = request.limit or 100,用户明确传 0 想禁用返回却得到 100。
诊断: 把 falsy 当 missing;使用 sentinel/is None 按业务语义判断。
面试追问
对象 truthiness 的判定顺序是什么?
回答要点: 类型可通过 __bool__ 定义;若没有,可参考 __len__;否则一般视为真。
训练 047 · for 的真实基础:迭代协议
基础题
把 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
基础题
为什么 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
基础题
调用一个包含 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
基础题
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
基础题
obj.method(1) 为什么可粗略理解成 Cls.method(obj, 1)?
揭晓: 函数作为 class attribute 是 non-data descriptor,访问时会创建 bound method,把实例绑定为第一个参数。
变体题
classmethod 第一个参数为什么是实际调用类而不是“定义它的那个类”固定值?
揭晓: descriptor 绑定的是调用上下文中的 class,因此继承时可获得子类。
真实开发 Bug
工厂方法硬编码 return Base(...),子类调用后仍返回 Base。
诊断: 如果希望多态构造,应使用 @classmethod 并 return cls(...)。
面试追问
什么时候 staticmethod 比模块级函数更合适?
回答要点: 行为概念上属于类命名空间、但不需要实例/类状态时;否则模块函数通常更简单。
训练 061 · MRO 与 super()
基础题
多继承中 super() 是“调用父类”还是“沿 MRO 调用下一个实现”?
揭晓: 后者。
变体题
为什么 cooperative multiple inheritance 要求链上的方法签名和 super() 使用方式互相兼容?
揭晓: 每一层都需要把调用继续传递给 MRO 下一节点;某层截断或参数不兼容会破坏整个链。
真实开发 Bug
一个 mixin 的 __init__ 没调用 super().__init__(),导致 MRO 后面的类初始化逻辑完全没执行。
诊断: 在 cooperative hierarchy 中每层都应遵守协作式 super() 协议。
面试追问
为什么不能把 super() 简单翻译为“我的父类对象”?
回答要点: 它是绑定了当前类型和实例/类的代理,根据 MRO 找下一个实现,并不是某个固定父类实例。
训练 065 · Attribute Lookup、__getattr__ 与 __getattribute__
基础题
__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
基础题
一个只实现 __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
基础题
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
基础题
同一进程中第二次 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
基础题
A import B,B 又 from A import x,但 A 尚未执行到 x = ...。为什么失败?
揭晓: B 看到的是 A 的半初始化 module object,x 尚不存在。
变体题
把 import 移进函数为什么“可能修复”但不一定是最佳设计?
揭晓: 它推迟导入时机,可能绕开初始化环;但真实问题往往是模块依赖方向/职责耦合。
真实开发 Bug
models.py import services.py,services.py 又 import models.py,随着功能增长不断出现循环。
诊断: 抽取共享协议/类型/常量到下层模块,重新设计依赖方向,而不是继续散布局部 import。
面试追问
import module 与 from module import name 在循环导入下哪个更容易受“名称尚未创建”影响?
回答要点: 后者需要导入当下立即解析具体名称,因此更直接暴露半初始化问题;前者可晚些通过 module 访问。
训练 082 · Type Hint 不改变动态类型本质
基础题
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
基础题
单核 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
基础题
“检查余额足够 → 扣减余额”由多条操作组成,即使单条操作各自安全,整体为什么仍会竞态?
揭晓: check-then-act 不是原子事务;其他执行单元可在中间改变共享状态。
变体题
加了 Lock 是否就自动避免所有并发问题?
揭晓: 不会;锁粒度、锁顺序、覆盖的数据不一致、死锁、跨进程状态等仍需设计。
真实开发 Bug
两个线程都执行 if key not in cache: cache[key] = expensive(),昂贵任务被执行两次。
诊断: 复合操作存在 race;需要用锁保护整个检查/创建临界区或使用并发安全的 single-flight 设计。
面试追问
为什么 GIL 不等于你的 Python 业务逻辑“线程安全”?
回答要点: GIL 控制解释器层执行权,不保证多条 Python 操作构成的业务不变量原子,也会在 IO/扩展等处切换。
训练 113 · 传统 GIL 心智模型
基础题
传统 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
基础题
Free-threaded build 的目标是什么?
揭晓: 允许 CPython 在不依赖传统全局 GIL 的构建中让多个线程并行执行 Python 代码,同时通过新的运行时机制维护对象安全。
变体题
“没有 GIL”是否意味着现有多线程代码无需锁?
揭晓: 恰恰相反;业务共享可变状态的 race 更需要显式同步。解释器保证内部结构安全,不等于你的不变量安全。
真实开发 Bug
迁移 free-threaded 后,原来“靠 GIL 碰巧没出问题”的共享 dict 读改写开始出现业务竞态。
诊断: 把隐式时序假设改成明确同步/消息传递;同时检查 C extension 的兼容性与是否会重新启用 GIL。
面试追问
为什么 free-threaded 不能简单说成“把 GIL 删除了就完事”?
回答要点: 需要替代引用计数、容器内部同步、内存分配等机制;性能、扩展兼容和对象生命周期行为也会变化。
训练 115 · Asyncio 核心模型
基础题
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 与结构化并发
基础题
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
基础题
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
基础题
默认 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
基础题
两个对象只互相引用、没有外部可达引用时,为什么 refcount 可能都不为零?
揭晓: 它们彼此贡献引用,因此需要 cyclic GC 判断这组对象从根不可达。
变体题
“Python 有 GC,所以引用计数不重要”为什么不对?
揭晓: CPython 常规对象生命周期主要仍高度依赖引用计数;cyclic GC 主要补充处理可参与循环的容器对象图。
真实开发 Bug
长期服务中大量对象互相注册回调形成环,虽最终可 GC,但高峰内存持续很久。
诊断: 可考虑打断无必要强引用、使用 weakref、显式注销,不能只说“GC 最后会收”。
面试追问
CPython 3.14 的 GC 分代模型和旧教程相比为什么需要核对版本?
回答要点: 3.14 调整了 GC generation 结构/行为;涉及代数、阈值和调优时必须以目标版本官方文档为准。
训练 144 · subprocess
基础题
运行固定命令时为什么通常优先:
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
基础题
为什么网络调用“有 retry 却没有 timeout”仍然可能永久卡住?
揭晓: 单次尝试没有失败边界,重试逻辑根本得不到执行机会。
变体题
HTTP POST 失败后是否一定可以安全重试?
揭晓: 不一定;如果服务端已处理但响应丢失,重试可能重复产生副作用。需考虑幂等语义/幂等键。
真实开发 Bug
支付请求客户端 timeout 后自动重试 3 次,最终用户被扣款两次。
诊断: timeout 不代表远端没执行;支付写操作需要 idempotency key、服务端去重和明确重试策略。
面试追问
设计 retry policy 至少考虑哪些维度?
回答要点: 哪些错误可重试、幂等性、最大次数/总时限、指数退避与 jitter、上下游容量及可观测性。
训练 150 · Transaction
基础题
三个相关 UPDATE 中第二个成功、第三个失败。transaction 的目标是什么?
揭晓: 把这组操作作为原子业务单元;失败时 rollback,避免只提交一半。
变体题
transaction 是否等于“拿一个全局锁,让其他事务都不能运行”?
揭晓: 不是;并发与隔离由数据库隔离级别/MVCC/锁机制等共同决定。
真实开发 Bug
函数内部先 commit(),然后再写审计表;审计失败后 rollback 已无法撤回前面的业务更新。
诊断: transaction boundary 切错;属于同一原子业务约束的写操作应放在同一事务边界内。
面试追问
应用层 transaction 和 connection pool 分别解决什么问题?
回答要点: transaction 管原子性/一致性边界;pool 管连接建立成本与复用,两者不是替代关系。
训练 156 · Fixture
基础题
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
基础题
只需要返回固定 API 响应,不关心调用次数,最接近 Stub 还是 Mock?
揭晓: Stub。
变体题
“Mock”为什么不应该成为所有 test double 的统称?
揭晓: 不同 double 关注点不同:Stub 提供结果、Spy 记录、Mock 验证交互、Fake 提供简化可运行实现等。
真实开发 Bug
单测把内部每个私有函数都 mock 掉,重构内部调用顺序后业务行为没变却几十个测试失败。
诊断: 测试过度绑定 implementation detail;应围绕可观察行为和真正的外部边界替换依赖。
面试追问
什么时候 Fake 比 Mock 更有价值?
回答要点: 当需要一个轻量但行为真实的替代实现(如 in-memory repository)来测试更完整协作,而不是只验证调用。
训练 159 · Patch 的位置
基础题
# 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 容器复杂度
基础题
对 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
基础题
缓存为什么不仅是“加一个 dict”?至少还要考虑哪两个问题?
揭晓: key 设计、失效/一致性、容量/淘汰、并发、命中率、错误缓存等。
变体题
functools.lru_cache 用在有副作用函数上有什么风险?
揭晓: 后续相同参数可能直接返回旧结果而跳过副作用;缓存要求函数语义适合按输入复用结果。
真实开发 Bug
权限查询缓存 1 小时,用户被撤权后仍能访问。
诊断: 缓存失效策略不满足安全一致性要求;缩短 TTL、事件驱动失效,或对高风险决策避免过强缓存。
面试追问
Cache stampede 是什么?
回答要点: 热 key 同时过期,大量请求并发回源;可用 single-flight、随机 TTL、提前刷新等控制。
训练 170 · N+1、批处理与 IO 性能
基础题
查询 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
基础题
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
基础题
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()
基础题
把用户输入传给 eval() 的核心风险是什么?
揭晓: 把“不可信数据”提升成“可执行 Python 代码”。
变体题
只把 globals={"__builtins__": {}} 传给 eval 是否就得到可靠安全沙箱?
揭晓: 不应这样假设;Python 对象模型可提供复杂逃逸路径,eval 不是为安全沙箱设计的。
真实开发 Bug
配置系统允许管理员输入公式并直接 eval(formula),后来权限边界变化,低权限用户也能编辑公式。
诊断: 不应依赖“现在只有可信人会输入”;改为解析受限表达式 DSL/AST 白名单或明确支持的运算集合。
面试追问
什么时候 ast.literal_eval 比 eval 合适?它又不解决什么?
回答要点: 只需解析 Python literal 结构时更安全;它不是通用表达式执行器,也仍需考虑输入大小/资源耗尽。
训练 182 · Command Injection
基础题
为什么:
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
基础题
生成密码重置 token 应用 random 还是 secrets?
揭晓: secrets。
变体题
给 random.seed() 一个“看起来很随机”的当前时间,是否适合安全 token?
揭晓: 不适合;Mersenne Twister 面向模拟/统计,输出可预测,不提供密码学安全保证。
真实开发 Bug
邀请码使用 random.randint(100000, 999999),攻击者可大量枚举且可能推断序列。
诊断: 如果邀请码承担认证/授权秘密,应提高熵并使用 secrets,同时结合速率限制/过期等机制。
面试追问
secrets 解决了 token 系统的全部安全问题吗?
回答要点: 只解决安全随机来源;还需长度/熵、存储、过期、单次使用、比较、防暴力破解等设计。
训练 189 · EAFP 与 LBYL
基础题
访问 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
基础题
“相同输入总得到相同输出且不修改外部状态”描述的是什么倾向?
揭晓: Pure function。
变体题
读取当前时间的函数没有写文件,为什么仍不算纯函数?
揭晓: 结果依赖隐式外部状态(时间),相同显式输入不保证相同输出。
真实开发 Bug
calculate_price(order) 内部偷偷读取全局促销配置和当前时间,单测在不同日期随机失败。
诊断: 把隐式依赖显式参数化(规则、clock),纯计算与 IO/状态读取分层。
面试追问
为什么纯函数常更容易缓存、并行和测试?
回答要点: 输入输出关系稳定,缺少共享副作用,结果可重现,依赖边界明确。
训练 192 · Composition vs Inheritance
基础题
“Car has an Engine” 更自然倾向 composition 还是 inheritance?
揭晓: composition。
变体题
为了复用 5 行代码就继承一个基类,为什么可能是坏设计?
揭晓: inheritance 建立的是更强的 is-a、替换性和生命周期耦合,不只是代码复用工具。
真实开发 Bug
业务类继承某 SDK Client 只为复用 request(),后来 SDK 增加初始化约束和方法名冲突,业务类被迫跟着变化。
诊断: 依赖能力而非“成为 SDK Client”;把 client 作为组合依赖注入更稳。
面试追问
什么时候 inheritance 反而是合适的?
回答要点: 存在稳定的 is-a 关系、基类明确设计为扩展点、子类能遵守替换契约,并且 MRO/生命周期可控。