>>>PyPathPython 学习站
前沿与实践 · Frontier & Practice lv.4 前沿 kp-047

常见误区与陷阱集

前置知识:kp-003、kp-007、kp-012

1. 一句话定义

本篇把散落在各知识点的"坑"汇成一张复习用速查表——每条都指向机制层解释,建议在阶段二/三结束时各过一遍。

2. 为什么重要

90% 的 Python bug 反复来自同一批机制误解。把它们集中复习一次的收益,远大于再学一个新语法特性;这也是面试的经典题库。

3. 前置知识

kp-003、kp-007、kp-012(机制层三地基)。

4. 核心概念(陷阱速查表)

#陷阱最小示例机制根源详见
1可变默认值def f(x=[])默认值定义时求值一次kp-007
2b = a 以为复制b.append 波及 a名字绑定kp-003
3浅拷贝嵌套坑a[:] 共享内层浅复制语义kp-028
4迭代器二次消费sum(it) 两次协议一次性kp-012
5迟绑定闭包循环里 lambda 共享 i捕获变量非值kp-023
6is 比值256 is 256 偶真小整数缓存kp-005
7sort 返回 Nonelst = lst.sort()原地方法约定kp-009
8类属性可变共享members = [] 类级属性查找顺序kp-017
9循环中删列表元素边遍历边 remove迭代器视图错位kp-006
10except: 吞一切裸 exceptBaseException 层kp-015
11GIL 下线程加速 CPU线程池跑计算字节码级单锁kp-029
12协程忘 awaitf() 未执行协程对象惰性kp-031
13f-string 拼日志log(f"{heavy()}")先求值后传参kp-037
14groupby 不排序相邻才分组惰性分组语义kp-038
15dict 遍历中增删RuntimeError哈希表结构变更kp-010
16== 比 float0.1+0.2==0.3 假IEEE754kp-004
17默认编码漂移Windows GBK平台默认编码kp-016
18忘 __main__ 守卫多进程递归爆spawn 重新 importkp-032

5. 原理与机制(三个"母题")

绝大多数陷阱收敛于三个母题,记住母题比记 18 条更有效:

text
母题 A【共享与绑定】:一切"改了不该改的"都源于
  多个名字引用同一个可变对象(kp-003)
母题 B【求值时机】:一切"为什么当时不是这个值"都源于
  定义时求值 vs 使用时求值的错位(默认值/闭包/装饰器)
母题 C【协议与惰性】:一切"怎么没反应/怎么只跑一次"都源于
  迭代器一次性、协程惰性、描述符拦截时机

6. 关键事实(模型/图示)

text
自我检查清单(写完代码过一遍):
  □ 有没有可变默认值/类属性可变共享?
  □ 有没有浅拷贝承担了深拷贝的期待?
  □ 迭代器/生成器有没有被二次消费?
  □ 循环 lambda 是否捕获了循环变量?
  □ 异常处理是否过宽?try 范围是否过大?
  □ 并发代码的共享状态有没有锁/队列?
  □ 异步函数里的阻塞调用清零了吗?

7. 直观类比

陷阱集像考驾照的违章扣分表:不是为了背下来应付考试,而是每一条背后都是一次真实事故(机制解释);过一遍扣分表(本篇)比重新撞一遍墙便宜得多。

8. 实例与案例(三个最经典的最小复现)

python
# ① 可变默认值
def add(x, bag=[]): bag.append(x); return bag
add(1); add(2)      # [1, 2]——跨调用累积

# ② 迟绑定
fs = [lambda: i for i in range(3)]
[f() for f in fs]   # [2, 2, 2]

# ③ 迭代器耗尽
it = iter([1, 2])
sum(it), sum(it)    # (3, 0)

9. 常见误区(关于误区本身的元误区)

  1. 背答案不背机制 —— 变体一出就露馅;每条陷阱都要能推回 kp-003/007/012。
  2. 把"运行没报错"当"没踩坑" —— 陷阱多是语义错误而非语法错误,静默出错才致命。
  3. 只在此清单内复习 —— 真正的疫苗是 wtfpython 与 Fluent Python 的完整案例库。

10. 自测题

  1. 用"母题 B"解释默认值陷阱与闭包迟绑定的共同根源。
  2. 为什么 sort() 返回 None 是有意的 API 设计而非疏忽?
  3. 陷阱 11 与 12 的共同点是什么?
参考答案
  1. 都是定义时求值:默认值在 def 时求值一次被共享;lambda 捕获的变量在循环结束时才被读取——"值取的时机"与"预期时机"错位。
  2. 强制区分"原地修改"与"返回新对象"两族 API:返回 None 让你无法把原地修改误当表达式使用(Pythonic 明确性优于便利性)。
  3. 都属于母题 C(惰性):线程按调度交错执行(等待也是惰性),协程对象创建后不自动执行。

11. 与其他知识点的关系

  • kp-003/007/012:三地基,每个陷阱的机制出口。
  • kp-035 测试:多数陷阱可用边界测试捕获。
  • kp-045 工程规范:ruff 的 B/SIM 规则族静态拦截其中一部分。

12. 延伸阅读

  • wtfpython(中英双语):https://github.com/satwikkansal/wtfpython
  • Fluent Python 各章 "Gotchas"