可变默认参数陷阱
在函数定义中使用可变对象(如列表、字典)作为默认参数是Python新手常犯的错误。当默认参数在函数定义时被求值后,后续调用会共享同一个可变对象:
def append_to(element, target=[]):
target.append(element)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] 非预期结果
根本原因在于Python的函数默认参数在定义时(def语句执行时)只被求值一次,而不是每次调用时重新创建。解决方案是使用不可变对象None作为默认值:
def append_to(element, target=None):
if target is None:
target = []
target.append(element)
return target
- 优点:完全避免共享状态问题
- 缺点:需要额外的条件判断
- 适用场景:所有需要可变默认参数的函数定义
迭代过程中修改集合
在遍历集合时直接修改其内容会导致RuntimeError
:
words = {'hello', 'world'}
for word in words:
words.remove(word) # RuntimeError
正确的处理方式包括:
- 遍历集合的副本
for word in words.copy():
words.remove(word)
- 使用推导式创建新集合
words = {word for word in words if ...}
底层原理:Python集合使用哈希表实现,迭代器维护着对原始集合的引用和当前索引位置。直接修改集合会导致迭代器状态失效。
- 方案选择建议:
- 小型集合适合
.copy()
- 大型集合适合推导式(内存效率更高)
- 小型集合适合
变量作用域混淆
Python的LEGB(Local, Enclosing, Global, Built-in)作用域规则常导致意外行为:
x = 10
def foo():
print(x) # UnboundLocalError
x = 20
解决方案包括:
- 明确声明全局变量
def foo():
global x
print(x)
x = 20
- 使用闭包时声明nonlocal
def outer():
x = 10
def inner():
nonlocal x
x += 1
return inner
最佳实践:
– 避免过度使用全局变量
– 函数式编程风格优先考虑返回值而非修改外部状态
– 类属性是更好的共享状态方案
浅拷贝与深拷贝误用
对象复制时的浅拷贝行为可能导致意外修改:
matrix = [[0]*3]*3
matrix[0][0] = 1 # 修改所有子列表的第一个元素
正确创建独立副本的方式:
- 列表推导式
matrix = [[0]*3 for _ in range(3)]
- 使用copy模块
import copy
new_obj = copy.deepcopy(nested_structure)
性能考量:
– 浅拷贝(copy.copy()
)时间复杂度O(1)
– 深拷贝(copy.deepcopy()
)时间复杂度O(n)
– 对于不可变对象,直接赋值即可(Python会优化处理)
GIL导致的性能误解
全局解释器锁(GIL)使得多线程在CPU密集型任务中无法实现真正的并行:
import threading
def count():
n = 0
for _ in range(1000000):
n += 1
threads = [threading.Thread(target=count) for _ in range(4)]
for t in threads:
t.start()
# 实际执行时间可能比单线程更长
替代方案:
- 多进程处理
from multiprocessing import Pool
with Pool(4) as p:
p.map(cpu_intensive_task, data)
- 使用C扩展或Cython
- 换用async/await处理I/O密集型任务
现代解决方案:
– Python 3.12引入的子解释器(PEP 684)
– 使用NumPy等基于C的库规避GIL
– 考虑PyPy等替代实现
异常处理不当
常见的异常处理反模式包括:
- 捕获过于宽泛的异常
try:
risky_call()
except: # 会捕获包括KeyboardInterrupt在内的所有异常
pass
- 忽略异常上下文
try:
parse(data)
except ValueError:
raise RuntimeError # 丢失原始异常信息
改进方案:
try:
process()
except SpecificError as e:
logger.error(f"Context: {e}")
raise CustomError from e # 显式链式异常
finally:
cleanup() # 确保资源释放
行业实践:
– 遵循EAFP(Easier to Ask for Forgiveness than Permission)原则
– 使用contextlib
管理资源
– 结构化日志记录异常信息