# 常见错误 本节详细讲解 Python 中最常见的编程错误和陷阱。 ## 可变对象陷阱 ### 可变默认参数 这是 Python 最著名的陷阱之一。 ```python # ❌ 危险:可变默认参数 def add_item(item, items=[]): items.append(item) return items print(add_item('a')) # ['a'] print(add_item('b')) # ['a', 'b'] 不是 ['b']! print(add_item('c')) # ['a', 'b', 'c'] # 为什么?默认参数在函数定义时只创建一次 # 后续调用共享同一个列表对象 # ✅ 正确做法 def add_item(item, items=None): if items is None: items = [] items.append(item) return items # 同样适用于字典、集合等可变对象 def process(data=None): if data is None: data = {} return data ``` ### 列表浅复制 ```python # ❌ 浅复制陷阱 original = [[1, 2], [3, 4]] copy = original[:] # 或 list(original) copy[0].append(5) print(original) # [[1, 2, 5], [3, 4]] 原列表也被修改! # 为什么?浅复制只复制外层列表,内层仍是引用 # ✅ 深复制 import copy original = [[1, 2], [3, 4]] deep = copy.deepcopy(original) deep[0].append(5) print(original) # [[1, 2], [3, 4]] 不受影响 # 简单情况可以用列表推导 copy = [row[:] for row in original] ``` ### 列表乘法创建二维数组 ```python # ❌ 错误方式 matrix = [[0] * 3] * 3 matrix[0][0] = 1 print(matrix) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] 所有行都变了! # 为什么?外层乘法创建的是同一列表的三个引用 # ✅ 正确方式 matrix = [[0] * 3 for _ in range(3)] matrix[0][0] = 1 print(matrix) # [[1, 0, 0], [0, 0, 0], [0, 0, 0]] ``` ## 作用域问题 ### UnboundLocalError ```python # ❌ 常见错误 x = 10 def foo(): print(x) # UnboundLocalError: local variable 'x' referenced before assignment x = 20 # 为什么?Python 在编译时确定 x 是局部变量(因为有赋值语句) # 但运行到 print 时,局部变量 x 还未赋值 # ✅ 解决方案1:使用 global def foo(): global x print(x) x = 20 # ✅ 解决方案2:使用不同变量名 def foo(): print(x) # 全局变量 y = 20 # 局部变量 # ✅ 解决方案3:作为参数传递(推荐) def foo(x): print(x) return x + 10 ``` ### nonlocal 与嵌套函数 ```python # ❌ 错误 def outer(): count = 0 def inner(): count += 1 # UnboundLocalError return count return inner # ✅ 正确 def outer(): count = 0 def inner(): nonlocal count # 声明使用外层变量 count += 1 return count return inner counter = outer() print(counter()) # 1 print(counter()) # 2 ``` ## 迭代陷阱 ### 修改正在迭代的容器 ```python # ❌ 危险:迭代时修改列表 items = [1, 2, 3, 4, 5] for i, item in enumerate(items): if item % 2 == 0: del items[i] # 索引会错乱 print(items) # 可能不是预期结果 # ❌ 同样危险 for item in items: if item % 2 == 0: items.remove(item) # 会跳过元素 # ✅ 正确:创建新列表 items = [x for x in items if x % 2 != 0] # ✅ 或者反向迭代 items = [1, 2, 3, 4, 5] for i in range(len(items) - 1, -1, -1): if items[i] % 2 == 0: del items[i] # ✅ 或者使用 filter items = list(filter(lambda x: x % 2 != 0, items)) ``` ### 迭代器耗尽 ```python # ⚠️ 迭代器只能使用一次 numbers = (x ** 2 for x in range(5)) print(list(numbers)) # [0, 1, 4, 9, 16] print(list(numbers)) # [] 空的! # ✅ 需要多次使用时,转为列表 numbers = list(x ** 2 for x in range(5)) # 或重新创建生成器 def get_squares(): return (x ** 2 for x in range(5)) ``` ### zip 的最短原则 ```python # ⚠️ zip 以最短的为准 names = ['Alice', 'Bob', 'Charlie'] scores = [90, 85] for name, score in zip(names, scores): print(f"{name}: {score}") # Charlie 被忽略! # ✅ 使用 zip_longest from itertools import zip_longest for name, score in zip_longest(names, scores, fillvalue=0): print(f"{name}: {score}") # ✅ Python 3.10+ 可以使用 strict # list(zip(names, scores, strict=True)) # ValueError ``` ## 比较陷阱 ### is vs == ```python # ⚠️ 小整数缓存 a = 256 b = 256 print(a is b) # True (因为 -5 到 256 被缓存) a = 257 b = 257 print(a is b) # False 或 True (取决于上下文) # ⚠️ 字符串驻留 s1 = "hello" s2 = "hello" print(s1 is s2) # True (字符串驻留) s1 = "hello world" s2 = "hello world" print(s1 is s2) # 可能是 False # ✅ 规则 # 用 == 比较值 # 用 is 只比较 None, True, False if x is None: pass if flag is True: # 或直接 if flag: pass ``` ### 浮点数比较 ```python # ❌ 浮点数精度问题 print(0.1 + 0.2) # 0.30000000000000004 print(0.1 + 0.2 == 0.3) # False! # ✅ 使用 math.isclose import math print(math.isclose(0.1 + 0.2, 0.3)) # True print(math.isclose(0.1 + 0.2, 0.3, rel_tol=1e-9)) # 自定义精度 # ✅ 金融计算使用 Decimal from decimal import Decimal result = Decimal('0.1') + Decimal('0.2') print(result == Decimal('0.3')) # True ``` ### 链式比较 ```python # ⚠️ Python 的链式比较可能不符合直觉 x = 5 print(1 < x < 10) # True (这是正确的) # 但注意: print(1 < x > 3) # 等价于 1 < x and x > 3 print((1 < x) > 3) # 不同!(1 < x) 是 True,然后 True > 3 是 False # ⚠️ 特别注意 is 的链式 a = b = [] print(a is b is []) # False! 等价于 a is b and b is [] ``` ## 字符串陷阱 ### 字符串不可变 ```python # ❌ 低效的字符串拼接 result = "" for i in range(10000): result += str(i) # 每次创建新字符串! # ✅ 使用 join result = "".join(str(i) for i in range(10000)) # ✅ 或使用 StringIO from io import StringIO buffer = StringIO() for i in range(10000): buffer.write(str(i)) result = buffer.getvalue() ``` ### 编码问题 ```python # ⚠️ 文件编码 # 不指定编码可能导致问题 with open('file.txt', 'r') as f: # 使用系统默认编码 content = f.read() # ✅ 显式指定编码 with open('file.txt', 'r', encoding='utf-8') as f: content = f.read() # ⚠️ bytes 和 str b = b'hello' s = 'hello' # b + s # TypeError! # ✅ 显式转换 s = b.decode('utf-8') b = s.encode('utf-8') ``` ## 异常处理陷阱 ### 捕获过于宽泛的异常 ```python # ❌ 捕获所有异常 try: do_something() except: # 或 except Exception: pass # 吞掉所有错误,包括 KeyboardInterrupt! # ✅ 捕获具体异常 try: do_something() except (ValueError, KeyError) as e: logger.error(f"Expected error: {e}") except Exception as e: logger.exception(f"Unexpected error: {e}") raise # 重新抛出 ``` ### finally 中的 return ```python # ⚠️ finally 中的 return 会覆盖其他 return def problematic(): try: return "try" except: return "except" finally: return "finally" print(problematic()) # "finally" # 甚至覆盖异常 def also_problematic(): try: raise ValueError("Error!") finally: return "finally" # 异常被吞掉! print(also_problematic()) # "finally",没有异常! # ✅ finally 只用于清理,不要 return ``` ### 异常中的变量作用域 ```python # ⚠️ Python 3 中,except 中的变量在块结束后被删除 try: raise ValueError("error") except ValueError as e: error = e # 保存引用 print(e) # print(e) # NameError: name 'e' is not defined print(error) # 这样可以 ``` ## 最佳实践总结 ::::{grid} 1 :gutter: 2 :::{grid-item-card} 避免陷阱的原则 1. **不使用可变默认参数**:用 `None` 代替 2. **理解引用语义**:复制时注意深浅 3. **不修改迭代中的容器**:创建新容器 4. **用 `==` 比较值**:`is` 只用于 `None` 5. **捕获具体异常**:不要裸 `except` 6. **显式指定编码**:打开文件时 ::: :::{grid-item-card} 调试技巧 ```python # 检查对象身份 print(id(obj)) # 检查可变性 print(type(obj).__mutable__) # 没有这个属性 # 一般来说:list, dict, set 可变;str, tuple, frozenset 不可变 # 检查引用 import sys print(sys.getrefcount(obj)) ``` ::: ::::