
最近卡卡在给团队的公共库写一个小工具:往购物车里加商品。实习生小豚图省事,把参数默认值写成了 box=[],结果上线后用户反馈:我的购物车里怎么有别人的苹果?
排查了半天接口、缓存、数据库,最后发现根本不关它们的事——是 python 的默认参数在"捣鬼"。这个坑太经典了,经典到几乎每个 python 程序员都会栽一次。今天卡卡就用逛超市的姿势,把它的来龙去脉讲清楚,看完你就是超市里最清醒的崽。
1、一段有"记忆"的代码
先看小豚的原始代码:
def add_item(name, box=[]):
box.append(name)
return box
print(add_item('苹果'))
print(add_item('香蕉'))
按直觉,两次调用应该各返回 ['苹果'] 和 ['香蕉'],每次都是崭新的购物车。
实际输出却是:
['苹果']
['苹果', '香蕉'] # 香蕉进来了,苹果还在?!
购物车不但没换新的,上一次的商品还整整齐齐躺在里面。这就是默认参数的"记忆"。
2、购物车为什么没换新的?
关键在于:默认参数只在函数定义的那一刻被求值一次,而不是每次调用时重新创建。
也就是说,def add_item(name, box=[]) 这行代码被执行时,python 就推了一辆车出来,之后所有的调用,用的都是同一辆车。函数在等参数的时候,车身早就站在那儿了。
想亲眼验证?python 把这个"车"直接挂在了函数身上:
def add_item(name, box=[]):
box.append(name)
return box
add_item('苹果')
print(add_item.__defaults__)
# (['苹果'],) ← 默认值就存在这,谁都能看
__defaults__ 是函数对象的元数据,存着所有默认参数的当前值。你往里 append,它就真的记住了——所谓"记忆",不过是这辆车从来没换过而已。
这也解释了为什么 box=[] 会出事,而 count=0 不会:数字是不可变对象,改不了原值;列表是可变对象,一改一个准。
3、正确姿势:每次都推新车
解法很简单:默认值只用来"表示没传",真正的新车在函数体里现推。
def add_item(name, box=None):
if box is None:
box = [] # 每次调用,现推一辆新车
box.append(name)
return box
print(add_item('苹果')) # ['苹果']
print(add_item('香蕉')) # ['香蕉'] 这次干净了
None 是不可变的,当哨兵最合适:没传就给新车,传了就用你的车,互不干扰。
如果你在用 dataclass,它连这个判断都帮你省了:
from dataclasses import dataclass, field
@dataclass
class Cart:
items: list = field(default_factory=list) # 每个实例一辆新车
a, b = Cart(), Cart()
a.items.append('苹果')
print(b.items) # [] b 的车是干净的
default_factory=list 的意思是:每次造实例时,调用一次 list 来生产新车。这个思路和 None 哨兵一脉相承——别提前买车,要用的时候再造。
总结
默认参数的"记忆"不是玄学:它在函数定义时只求值一次,可变对象就会把每次调用的痕迹攒在一起。记住两条铁律——可变对象永远不做默认值;想"每次都是新的",就用 None 哨兵或 default_factory。下次再看到 def f(a=[]),请把这篇文章甩给 ta,再顺手点个赞,卡卡在超市感激不尽!
以上就是“别再写 def f(a=[]) 了!python 默认参数竟然有"记忆"”的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。
扫码二维码 获取免费视频学习资料

- 本文固定链接: http://www.phpxs.com/post/14525/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料