因为默认参数在函数定义时求值一次,可变对象如list、dict会被所有调用共享,导致数据累积;应使用None占位并在函数体内初始化。

为什么 list 或 dict 不能当默认参数用
因为默认参数在函数定义时就求值一次,不是每次调用才新建。如果用了可变对象(比如 []、{}),所有调用会共享同一个对象,导致“上一次调用的修改影响下一次”。常见现象是:第一次调用正常,第二次开始数据莫名累积。
比如写 def append_to(item, lst=[]),连续调用两次 append_to(1) 和 append_to(2),第二次的 lst 里会同时有 1 和 2——这不是 bug,是语言机制决定的。
- 所有 Python 版本都这样,不是版本差异问题
- 类方法中用
self.xxx = []是安全的,但作为默认参数传入就不行 - 哪怕函数没显式修改它,只要内部调用了
.append()、.update()这类原地操作,就会污染
正确的默认空列表/字典怎么写
统一用 None 占位,函数体内再按需初始化。这是最清晰、最无歧义的写法。
def append_to(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
- 必须用
is None判断,不用== None(虽然通常等效,但风格和语义更准) - 不要写成
if not lst:—— 如果用户真传了空列表,这逻辑就误判了 - 如果想支持传入
False、0等假值且仍保持默认行为,is None是唯一可靠方式
有没有例外情况?真需要共享状态怎么办
极少数场景下,你确实想要跨调用共享一个对象(比如缓存、计数器),那用可变默认参数反而是有意为之的设计。但必须加注释,且确保调用方知情。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
def count_calls(msg="hello", counter=[0]): # 注意:故意用 list 共享状态
counter[0] += 1
return f"{msg} (call #{counter[0]})"
- 这种写法容易被后续维护者误改,风险高
- 更推荐用装饰器、闭包或类封装来显式表达“共享状态”意图
- 如果只是临时调试打点,用模块级变量比默认参数更易读
类型提示里怎么写才不误导人
类型提示要跟运行时行为一致。如果实际接受 None 并内部转成 list,标注就得体现这个契约。
from typing import List, Optional <p>def append_to(item: int, lst: Optional[List[int]] = None) -> List[int]: if lst is None: lst = [] lst.append(item) return lst</p>
- 别写成
lst: List[int] = []—— 类型检查器会报错,而且和运行时不符 - 用
Optional[...]明确告诉使用者:这个参数可以为None - 如果函数内部不修改传入的
lst,而只读,可以用Sequence或Iterable更准确
默认参数的坑不在语法难,而在它违反直觉——你以为每次调用都是新对象,其实不是。只要记住“可变对象只初始化一次”,并养成 None 占位的习惯,就能避开绝大多数问题。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










