作为一名长期深耕Python领域的开发者,我在日常工作中积累了不少经验教训。今天想记录一些看似简单却容易导致严重后果的编程陷阱,这些都是在实际项目中真实遇到过的问题。

可变默认参数的隐患

这是一个经典但依然常见的错误。许多开发者会在函数定义中使用可变对象作为默认参数:

def add_item(item, items=[]):
    items.append(item)
    return items

# 看似正常的使用
print(add_item(1))  # [1]
print(add_item(2))  # [1, 2] - 意外结果!

问题在于,默认参数在函数定义时就被创建并绑定,而不是每次调用时重新创建。正确的做法是:

def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

浅拷贝与深拷贝的混淆

Python的赋值操作并不会创建新对象,而是创建新的引用。这在处理嵌套数据结构时尤其危险:

original_list = [[1, 2], [3, 4]]
shallow_copy = original_list.copy()

# 修改嵌套元素会影响原列表
shallow_copy[0][0] = 99
print(original_list)  # [[99, 2], [3, 4]] - 意外修改!

解决方案是使用深拷贝:

import copy
original_list = [[1, 2], [3, 4]]
deep_copy = copy.deepcopy(original_list)

异常处理的粒度问题

过于宽泛的异常捕获会掩盖真正的问题:

# 不推荐的写法
try:
    result = process_data()
    save_to_database(result)
    send_notification()
except Exception as e:
    print(f"发生错误: {e}")

这种写法无法区分不同类型的错误。更好的做法是:

try:
    result = process_data()
except ValueError as e:
    print(f"数据处理错误: {e}")
    return

try:
    save_to_database(result)
except DatabaseError as e:
    print(f"数据库错误: {e}")
    # 可能需要回滚操作
    return

循环中修改集合的陷阱

在遍历列表或字典时修改它们会导致意外行为:

items = [1, 2, 3, 4, 5]

# 错误的做法
for item in items:
    if item % 2 == 0:
        items.remove(item)  # 这会改变迭代过程

print(items)  # [1, 3, 5] - 可能不是预期结果

安全的做法是创建副本或使用列表推导式:

# 方法1:创建副本
for item in items[:]:
    if item % 2 == 0:
        items.remove(item)

# 方法2:列表推导式
items = [item for item in items if item % 2 != 0]

浮点数比较的精度问题

直接比较浮点数可能导致意外的结果:

# 可能返回False
print(0.1 + 0.2 == 0.3)  # False

正确的比较方式:

import math

# 使用容差比较
math.isclose(0.1 + 0.2, 0.3)  # True

# 或者自定义容差
def float_equal(a, b, tolerance=1e-9):
    return abs(a - b) < tolerance

字符串连接的性能问题

在循环中使用+连接字符串会导致性能问题:

# 低效的做法
result = ""
for i in range(10000):
    result += str(i)

应该使用join方法:

# 高效的做法
result = ''.join(str(i) for i in range(10000))

忽略上下文管理器的价值

手动管理资源容易出错:

# 容易忘记关闭文件
file = open('data.txt', 'r')
data = file.read()
# 如果中间发生异常,文件可能不会关闭
file.close()

使用上下文管理器更安全:

with open('data.txt', 'r') as file:
    data = file.read()
# 文件会自动关闭

对于自定义资源,可以实现上下文管理器协议:

from contextlib import contextmanager

@contextmanager
def database_connection(connection_string):
    conn = create_connection(connection_string)
    try:
        yield conn
    finally:
        conn.close()

这些经验教训都是通过实际项目中的痛苦调试过程获得的。希望这些记录能帮助其他开发者避免类似的陷阱,写出更健壮、可维护的Python代码。