python中可变默认参数陷阱源于函数定义时复用同一可变对象,导致多次调用间数据累积;应始终用none作默认值并在函数内初始化。

Python 中的可变默认参数陷阱
函数定义时用可变对象(如 list、dict)作默认参数,是数据不一致最隐蔽的源头。它不是“偶尔出错”,而是每次复用该默认对象时都会累积副作用。
常见错误现象:append() 多次调用后列表越积越多,或字典键值意外残留上一次调用的数据。
- 正确写法永远用
None作默认值,在函数体内初始化:def f(items=None): items = items or [] - 注意
or在items是空列表时会误判为 False,更安全的是items = [] if items is None else items - 这种问题在类方法、装饰器、回调注册等场景中尤其危险——对象生命周期长,副作用持续存在
多线程下共享变量未加锁
threading 模块里直接读写全局变量或实例属性,几乎必然导致数据不一致。CPython 的 GIL 只保原子操作安全,count += 1 这种看似简单的语句实际包含读取、计算、写入三步,中间可能被切换。
使用场景:计数器、缓存更新、状态标记等轻量共享数据。
立即学习“Python免费学习笔记(深入)”;
- 优先用
threading.Lock包裹临界区,而不是依赖“应该不会同时进来”的侥幸 -
queue.Queue是线程安全的,适合生产者-消费者模式;但别把它当通用容器去 get()/put() 非队列逻辑的数据 - 避免用
threading.local()试图“绕过锁”——它解决的是数据隔离,不是一致性
异步代码中混用同步 I/O 导致状态错乱
asyncio 环境下调用 time.sleep()、requests.get() 或直接读写文件,会阻塞整个事件循环,让其他协程无法调度,进而破坏预期的执行顺序和状态更新时机。
典型表现:多个 async def 函数看似并发,实际串行执行;数据库写入顺序与日志打印顺序不符;超时判断失效。
- 用
asyncio.sleep()替代time.sleep() - http 请求必须换
aiohttp或httpx.AsyncClient;文件操作用anyio或aiopath,别碰open() - 哪怕只有一处同步调用,整个协程链就失去并发保证——检查所有 await 点上下游
ORM 对象状态与数据库实际不一致
django ORM 和 sqlAlchemy 默认启用对象缓存和延迟加载,obj.field 返回的未必是数据库最新值,尤其是跨请求、跨线程或手动修改了 DB 后没刷新对象时。
常见于后台任务更新数据后,Web 请求仍读到旧值;或并发更新同一记录引发覆盖写入。
- Django 用
obj.refresh_from_db()强制重载字段;SQLAlchemy 用session.refresh(obj) - 避免长期持有 ORM 实例——比如放进全局变量或缓存里反复用,它本质是个快照
- 高并发写场景优先用原子 SQL 更新(
update(...).where(...)),而非先查再改再 save
真正难处理的不是某一行代码写错,而是这些机制在组合使用时互相掩盖:比如异步任务里用了带可变默认参数的工具函数,又去读一个没 refresh 的 ORM 对象——问题会层层延迟暴露,最后出现在最意想不到的地方。