编程学习网 > 编程语言 > Python > 别再写 def f(a=[]) 了!python 默认参数竟然有"记忆"
2026
09-17

别再写 def f(a=[]) 了!python 默认参数竟然有"记忆"


最近卡卡在给团队的公共库写一个小工具:往购物车里加商品。实习生小豚图省事,把参数默认值写成了 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教程欢迎持续关注编程学习网。 

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取