Python这些诡异的Bug,你遇到过几个?
在 Python 中有许多有趣且令人困惑也可以说诡异的经典bug,它们往往源于语言特性、设计决策或意外行为。 你遇到过几个呢?
1.可变默认参数陷阱
现象:函数默认参数在多次调用间共享同一对象
def append_to(value, target=[]):
target.append(value)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] 而非预期的 [2]
原因:默认参数在函数定义时创建一次,后续调用复用该对象
修复:使用不可变默认值 + 内部创建新对象
def safe_append(value, target=None):
if target is None:
target = []
target.append(value)
return target
2.列表迭代时删除元素
现象:遍历列表时删除元素导致跳过部分项
numbers = [1, 2, 3, 4]
for num in numbers:
if num % 2 == 0:
numbers.remove(num)
print(numbers) # 输出 [1, 3] 而非预期的 [1, 3, 4]?
# 实际是 [1, 3](元素3未被处理)
原因:动态修改列表导致内部迭代器错位
修复:创建副本或使用列表推导式
numbers = [n for n in numbers if n % 2 != 0]
3.闭包延迟绑定
现象:闭包捕获循环变量的最终值
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs]) # [2, 2, 2] 而非预期的 [0,1,2]
原因:所有闭包共享变量 i,执行时获取最终值
修复:通过默认参数立即绑定值
funcs.append(lambda i=i: i) # 创建参数快照
4.is与==的混淆
现象:小整数缓存导致 is 的意外结果
a = 256
b = 256
a is b # True(Python 缓存小整数)
x = 257
y = 257
x is y # False(可能,取决于解释器实现)
原因:is 检查对象标识而非值,Python 缓存 [-5, 256] 的整数
原则:永远用 == 比较值,is 仅用于 None/单例对象
5.元组可变陷阱
现象:元组内含可变对象时"改变"
t = ([1, 2], 3)
t[0].append(3) # 成功!元组变为 ([1,2,3], 3)
原因:元组存储的是对象的引用,引用不变即可
启示:元组的"不可变"仅限顶层引用
6.浮点数精度问题
现象:浮点运算的微小误差
0.1 + 0.2 == 0.3 # False!
0.1 + 0.2 # 0.30000000000000004
原因:二进制浮点数无法精确表示某些十进制小数
修复:使用 math.isclose() 或 decimal 模块处理精度
7.作用域混淆
现象:在局部作用域内意外修改全局变量
x = 10
def func():
x += 1 # UnboundLocalError!
原因:赋值操作使 x 被视为局部变量
修复:明确声明 global x 或使用不同变量名
8.+=操作符的陷阱
现象:对元组使用 += 产生意外结果
a = ([1], [2])
a[0] += [3] # 抛出 TypeError,但 a 变为 ([1,3], [2])
原因:+= 先尝试原地修改(失败),再赋值(成功)
教训:避免对不可变容器内的可变对象使用复合赋值
9.字典键冲突
现象:不同对象作为字典键却指向相同槽位
class Key:
def __hash__(self):
return 1
def __eq__(self, other):
return True
d = {}
d[Key()] = 'A'
d[Key()] = 'B'
print(d) # 仅有一个键值对:{<Key object>: 'B'}
原因:哈希冲突 + __eq__ 返回 True 导致键被视为相同
启示:确保 __hash__ 和 __eq__ 逻辑一致
10.生成器return的误解
现象:生成器中的 return 被忽略
def gen():
yield 1
return "End"
g = gen()
next(g) # 1
next(g) # 抛出 StopIteration,但无法获取 "End"
正确用法:通过 StopIteration.value 或 yield from 捕获返回值
value = next(g, None) # 1
try:
next(g)
except StopIteration as e:
print(e.value) # 输出 "End"
这些bug揭示了 Python 中一些反直觉的特性,理解其原理能帮助避免常见陷阱。建议在重要项目中:
1.使用静态分析工具(如 PyLint)
2.编写单元测试覆盖边界情况