当前位置:首页>python>Python 新手必读:这 5 个变量坑会让你 debug 到怀疑人生

Python 新手必读:这 5 个变量坑会让你 debug 到怀疑人生

  • 2026-10-11 08:27:07
Python 新手必读:这 5 个变量坑会让你 debug 到怀疑人生

什么是变量?打个比方,你桌上有杯咖啡,你在杯子上贴了张便签写着"我的"。这个便签就是变量名,咖啡就是数据。你撕下便签贴到旁边的奶茶上 ,变量名没变,但它指向的东西变了。

Python里的变量就是这么回事——它不是盒子,它是便签。

至于作用域,说白了就是这张便签在哪个范围内有效。你在会议室里贴的便签,出了会议室门就没人认了。函数里定义的变量,函数外  面也看不到。

变量和作用域的概念本身不难,难的是它们组合在一起之后会冒出来的各种坑。下面这几个bug,我基本都亲手踩过,每一个单独拿出来都能被写成面试题。


坑 1:链式赋值的引用陷阱

今天这个,是我实习时候的翻车现场,当年被老板拿来讲了好久。

a = b = []
a.append(1)
print(b)

你觉得输出是什么?想好了吗?

实际是:[1]

链式赋值 a = b = [] 让 a 和 b 指向同一个列表对象。这不是创建了两个空列表,而是创建了一个空列表然后让两个变量都引用它。对 a 的修改自然会反映到 b 上。

正确写法:


a = []
b = []
a.append(1)
print(b)  # []

踩坑现场:初始化多个缓存字典用了 a = b = {},结果两个缓存互相污染,线上数据全乱了。


坑 2:字典推导式变量泄漏

Stack Overflow 上这道题被问了几万次,答案都在评论里吵架。


x = 'before'
lst = [x for x in range(3)]
print(x)

你觉得输出是什么?想好了吗?

实际是:before

在 Python 3 中,列表推导式有自己的作用域,不会泄漏变量。但在 Python 2 中,x 会被覆盖为 2。如果你的代码需要兼容 Python 2,这就是一个陷阱。

正确写法:


# Python 3 中推导式不泄漏变量
# 但 for 循环会:
for x in range(3):
    pass
print(x)  # 2,for 循环的变量会泄漏

踩坑现场:维护一个 Python 2/3 兼容的项目,某个函数在 Python 3 上正常但 Python 2 上出错,排查发现是推导式变量泄漏。


坑 3:循环中修改列表

教程里不写,文档里提一句就过,但这个问题能让你绕路绕很久。


nums = [1, 2, 3, 4, 5, 6]
for i, n in enumerate(nums):
    if n % 2 == 0:
        nums.remove(n)
print(nums)

你觉得输出是什么?想好了吗?

实际是:[1, 3, 5, 6]

在遍历列表的同时修改它,会导致迭代器跳过某些元素。当你删除索引 1 处的 2 后,原来索引 2 处的 3 变成了索引 1,但迭代器已经移到索引 2,直接跳到了 4。所以 6 被跳过了没有被检查到。

正确写法:


nums = [1, 2, 3, 4, 5, 6]
nums = [n for n in nums if n % 2 != 0]
print(nums)  # [1, 3, 5]

踩坑现场:批量清理数据库查询结果中的无效记录,上线后总有零星脏数据漏掉,查了好久才定位到这个问题。


坑 4:全局变量赋值前引用

这个行为是 Python 的设计决定,但很多人到现在还以为是 bug。

报错:UnboundLocalError: local variable 'x' referenced before assignment

x = 10

def foo():
    print(x)
    x = 20

foo()

Python 在编译函数时,发现函数体内有 x = 20 的赋值语句,就把 x 标记为局部变量。然后执行 print(x) 时,局部变量 x 还没赋值,就报 UnboundLocalError。Python 不会先去全局找 x 再切回局部。

修复:


x = 10

def foo():
    global x  # 明确声明使用全局变量
    print(x)
    x = 20

foo()

踩坑现场:在函数里想先读取再修改一个全局配置,运行直接报错,新手最容易在这里困惑。


坑 5:闭包中的变量绑定

有个同事把这个错误带进了 release,上线前一分钟才被抓出来,全组出了一身冷汗。


funcs = []
for i in range(5):
    funcs.append(lambda: i)

print([f() for f in funcs])

你觉得输出是什么?想好了吗?

实际是:[4, 4, 4, 4, 4]

lambda 捕获的是变量 i 的引用,而不是它在创建时的值。当循环结束后,i 的最终值是 4,所以所有 lambda 调用时都拿到 4。这是 Python 闭包的经典陷阱。

正确写法:


funcs = []
for i in range(5):
    funcs.append(lambda i=i: i)  # 默认参数绑定当前值

print([f() for f in funcs])  # [0, 1, 2, 3, 4]

踩坑现场:写一个按钮回调注册函数,5 个按钮点哪个都触发最后一个的逻辑,用户反馈说界面有 bug。


这 5 个坑不复杂,但每个都能让你多花半天时间。提前知道,值。

踩过类似的坑?说出来大家一起长个教训。

下期预告:数据类型专场


关于作者

写了 10 年 Python,赶上了机器学习的热潮,又撞上了大模型的浪头,每次以为学明白了,行业又变了一次。不是什么大神,就是一个在互联网里摸爬滚打、把坑踩了个遍的老工人。还在写代码,还在折腾,还在想明白这个时代到底发生了什么。把自己踩过的坑、走过的弯路、看过的热闹,说给还在路上的人听。

关注「鲁叶的Python」,一起穿越迷茫期。

最新文章

随机文章