代码语言

知识点思维导图

25 个知识节点

Python(35) - A1 - 附录-Python陷阱对照

你在 JS 里被 var 提升、== 隐式转换、对象引用复制坑过——Python 也有一组"长得人畜无害、踩下去很疼"的坑。本篇专治前端转 Python 最高频的四类翻车:可变默认参数、循环里的闭包、is== 混用、深浅拷贝。每个坑都给出 JS 对照,告诉你哪里"和 JS 一样别慌"、哪里"和 JS 不一样要改心智"。

一、切入点:你早就被语言坑过

JS 有它的"经典面试题坑":for (var i...) 循环里 setTimeout 全打印同一个值、[] == ![]trueconst obj2 = obj1 改一个动另一个。Python 的坑结构上一一对应,只是触发点换了位置。把这篇当成"Python 版的 JS 怪异题集"来读最省力。

JS 的经典坑 Python 的对应坑 本篇章节
for (var i...) + 异步闭包打印同值 循环里 lambda/闭包共享变量
== 隐式类型转换乱套 is 当成 ==
obj2 = obj1 引用复制改一动二 列表/字典赋值与浅拷贝
函数参数对象被意外共享 可变默认参数只创建一次

二、可变默认参数:函数的"默认值"只生成一次

这是 Python 独有、JS 没有对应物的头号坑,新手几乎必踩。

为什么会这样(WHY):Python 的默认参数值在函数定义时只计算一次,之后所有调用共享同一个对象。items=[] 这个空列表在 def 执行时就被创建好、绑死在函数上,不是每次调用都新建。

JS 程序员的直觉来自这里——JS 的默认参数是每次调用都重新求值的:

// JS:默认值每次调用都重新生成一个新数组,所以没有这个坑
function append(item, items = []) {
  items.push(item)
  return items
}
append(1)  // [1]
append(2)  // [2]  —— 干净,互不影响

正确写法:默认值用 None(不可变哨兵),在函数体里再创建:

记忆口诀:默认参数永远别用可变对象([] {} set())当默认值,一律用 None 占位再在体内创建。 这条规则没有例外,背下来。


三、循环里的闭包:变量是"晚绑定"的

这个坑 JS 老手会秒懂,因为 var 时代天天踩。

为什么(WHY):闭包捕获的是变量本身的引用,不是定义那一刻的值。等循环跑完,i 已经停在 2,三个 lambda 此时才被调用,读到的都是最终的 i=2。这和 JS var 的行为一模一样:

// JS:var 版本完全同款翻车
var funcs = []
for (var i = 0; i < 3; i++) {
  funcs.push(() => i)
}
console.log(funcs.map(f => f()))  // [3, 3, 3] —— 同样的坑

JS 的解法是改用 let(块级作用域,每轮新建绑定)。但 Python 没有 let,也没有块级作用域——这就是边界差异:你不能照搬 JS 的修法。Python 的解法是用默认参数在定义时"快照"当前值:

有意思的是:第一节"默认参数只求值一次"是个坑,这里反过来利用同一个机制来解题。理解了机制,坑和技巧是一体两面。

边界提醒:Python 的循环变量 i 在循环结束后依然存在(没有块级作用域),这点也和 JS 的 let 不同——别以为出了 for 它就消失了。


四、is vs ==:身份相等 vs 值相等

JS 里你纠结 =====;Python 里换成纠结 ==is,但对应关系不是你以为的那样

先建立锚点,再立刻划清边界:

比较 JS Python 比的是什么
值相等 ==(带隐式转换) ==(不隐式转换) 内容是否相等
严格相等 === —— (Python 的 == 已经不转换类型)
引用/身份相等 ===(对象比较引用) is 是不是同一个对象

关键差异:Python 的 == 不做 JS 那种离谱的隐式类型转换(不会有 [] == ![] 这种鬼故事),所以日常比较值用 == 就对了。is 比的是"是不是内存里同一个对象",约等于 JS 对象用 === 比引用。

最大的坑:用 is 比较数字/字符串,会被"小整数缓存"骗到。 Python 把 -5256 的小整数预先缓存复用,导致 is 时灵时不灵。下面用**交互式 REPL(逐行输入)**演示,这样才能稳定看到差异:

注意:这个 False 只在逐行输入的 REPL 里稳定出现。 如果把 x = 257 / y = 257 写进同一个 .py 脚本一起运行,编译期的"常量折叠"可能让 257 is 257 也变成 True——也就是说结果会随运行方式而变(CPython 实现细节,别依赖)。

正因为 is 的结果如此不可靠,所以比较值永远用 ==,绝不用 isis 只用在一个地方:和单例 None / True / False 比较。

记忆口诀:is None 是唯一该用 is 的高频场景;比较内容一律 ==


五、深浅拷贝:b = a 不会复制内容

这个坑 JS 程序员完全有共鸣——const b = a 对对象/数组只是复制引用,改一个动另一个。Python 一模一样。

// JS 同款行为
const a = [1, 2, 3]
const b = a              // 只复制引用
b.push(4)
console.log(a)           // [1, 2, 3, 4]

那怎么真正复制?这里 Python 比 JS 多分了两层概念——浅拷贝 vs 深拷贝

浅拷贝(shallow):只复制最外层容器,里面的子对象还是共享引用。常见写法有 copy.copy(x)list(x)x[:]dict(x){**x}

深拷贝(deep):递归复制所有层级,得到一棵完全独立的新树,用 copy.deepcopy(x)

对照 JS 你就秒懂——这正是 JS 里"浅拷贝 vs 真·深拷贝"的同一组概念:

操作 JS Python 复制深度
引用赋值(不拷贝) const b = a b = a 0 层,同一个对象
浅拷贝 [...a] / {...a} / Object.assign({}, a) a[:] / list(a) / {**a} 1 层
深拷贝 structuredClone(a) copy.deepcopy(a) 全部层级

边界提醒:JS 的 [...a]{...a} 也是浅拷贝,嵌套对象同样共享——所以这个"浅拷贝只管一层"的坑你其实在 JS 早见过,规则完全迁移得过来。需要彻底独立时,JS 用 structuredClone,Python 用 copy.deepcopy


六、附赠几个高频小坑(认脸即可)

这些不展开,先混个眼熟,遇到不至于懵:

小坑 JS 直觉 Python 真相
/ 除法 7/2 === 3.5 一致,但 4/2 也是 2.0(float);要整数用 //
链式比较 1 < 2 < 3 在 JS 是 bug Python 原生支持,是正确语法
and/or `
可变对象做 key 对象可当 Map 的 key list/dict/set 不可哈希,要用 tuple

七、总结

  • 切入点:你早就被语言坑过:Python 的坑结构上一一对应,只是触发点换了位置。
  • 可变默认参数:函数的"默认值"只生成一次:这是 Python 独有、JS 没有对应物的头号坑,新手几乎必踩。
  • 循环里的闭包:变量是"晚绑定"的:这个坑 JS 老手会秒懂,因为 var 时代天天踩。
  • is vs ==:身份相等 vs 值相等:Python 里换成纠结 == 和 is,但对应关系不是你以为的那样。
  • 深浅拷贝:b = a 不会复制内容:这个坑 JS 程序员完全有共鸣——const b = a 对对象/数组只是复制引用,改一个动另一个。
  • 附赠几个高频小坑(认脸即可):| / 除法 | 7/2 === 3.5 | 一致,但 4/2 也是 2.0(float);