Python函数默认参数在定义时只求值一次,若为可变对象(如[]、{}、set()),则所有调用共享同一实例,导致修改累积;正确做法是用None作默认值并在函数内初始化。

为什么 def func(x=[]) 会“记住”上次调用的修改
Python 函数定义时,default argument 只被求值一次——在函数对象创建时,而非每次调用时。如果默认值是可变对象(如 []、{}、set()),它就变成所有调用共享的同一个对象。后续对它的修改(如 .append())会持续累积。
常见错误现象:func() 第一次返回 [1],第二次返回 [1, 1],第三次变成 [1, 1, 1]……这不是 bug,是语言机制使然。
- 使用场景:写工具函数时图省事直接写
def add_item(item, items=[]) - 本质原因:
[]是 mutable object,绑定到函数的__defaults__元组中,生命周期与函数相同 - 验证方式:打印
func.__defaults__可看到那个“悄悄变长”的列表
正确写法:用 None 作占位符再初始化
这是最通用、最易读、也最符合 Python 惯例的解法。把可变默认参数显式延迟到函数体内部构造。
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
- 永远不要用
if not items:判断,因为[]、0、False、None都为假,会误判空列表 - 若需默认值是特定内容(如预填充的
[0]),仍应写成items=None,然后items = [0] if items is None else items - 该模式在标准库中广泛使用,比如
json.loads(s, object_hook=None)
哪些类型算“可变默认参数陷阱”的高危对象
只要对象支持原地修改(in-place mutation)且不是每次调用都新建,就可能踩坑。不只是 list。
本文档主要讲述的是Python之模块学习;python是由一系列的模块组成的,每个模块就是一个py为后缀的文件,同时模块也是一个命名空间,从而避免了变量名称冲突的问题。模块我们就可以理解为lib库,如果需要使用某个模块中的函数或对象,则要导入这个模块才可以使用,除了系统默认的模块(内置函数)不需要导入外。希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
立即学习“Python免费学习笔记(深入)”;
-
[]、list():最常见,.append()/.extend()后状态残留 -
{}、dict():.update()、key=value赋值后字典持续增长 -
set():.add()、.update()导致元素不断堆积 -
bytearray()、自定义类实例(含可变属性):同样适用此规则 - 安全的默认值:
None、True、42、"hello"、tuple()—— 不可变或无 in-place 修改语义
IDE 和 linter 能帮你发现这类问题吗
可以,但依赖配置。主流工具默认不会报错,需启用对应检查项。
-
pylint:开启dangerous-default-value(代码警告码 W0102) -
flake8:配合插件flake8-bugbear,触发B006(mutable argument used as default) - PyCharm:默认高亮可变默认参数,并提示 “Mutable default argument … may cause unexpected behavior”
- 注意:这些工具不会检测运行时动态构造的可变对象(如
def f(x=time.time())虽然不可变,但不推荐;而def f(x=get_config_dict())若返回同一 dict 实例,同样危险)
真正容易被忽略的是嵌套结构里的可变对象,比如默认参数是 {"data": []} —— 外层 dict 不可变,但内层 [] 仍是共享的。这种得靠人眼识别,工具通常不覆盖。









