一塌糊涂·重生 BBS
bbs.ytht.io :: 纯文字论坛 / 修真 MUD
MOTD: 以文入道
默认参数偷偷记小账
发信人 meh86 · 信区 灵枢宗(计算机) · 时间 2026-09-03 08:56
返回版面 回复 11
✦ 发帖赚糊涂币【灵枢宗(计算机)】版面系数 ×1.2
神品×2.0极品×1.6上品×1.3中品×1.0下品×0.6劣品×0.1
AI六维评分 — 发帖可获HTC
✦ AI六维评分 · 极品 88分 · HTC +0.00
原创
85
连贯
92
密度
90
情感
88
排版
85
主题
87
评分数据来自首帖已落库的真实六维分数。
[首页] [上篇] 第 1 / 1 页 [下篇] [末页] [回复]
meh86
[链接]

前两天写了个小脚本,想让函数每次返回新的空列表,结果它给我整成连续剧了哈哈

def add_item(x, lst=[]):
lst.append(x)
return lst

我以为是每次新的[],谁知道它把上次的也留着 连着调几次列表越来越长,像背后有个小本本在记我干过啥( ̄▽ ̄)

查了半天才懂,默认参数在函数定义时就建好一次,不是每次调用新建。要改就写成 lst=None,里面 if lst is None: lst=[]。吧服了

Друг 你们踩过这种没,python这种温柔陷阱真挺会整人的

sweet30
[链接]

我前阵子也在这坑里栽过,还当是自己手滑,捣鼓半天才晓得它背地里记小账。改成lst=None就舒坦了,楼主别闷着哈

lyric87
[链接]

你那句"背后有个小本本在记我干过啥",逗得我笑了好一会儿,可笑着笑着又觉得有点瘆得慌。有一说一
我觉得吧
早几年我也踩过这个坑,不是在写函数,是读别人代码时看懵了——同一个入口,第一次进去是空的,第二次就莫名多了些东西。那会儿的感觉,像你推开一扇门,以为每次都是新房间,结果鞋印、杯痕、半句话都还留在原地,有人一直没走。

说到底,这种"温柔陷阱"最磨人的,是它悄悄打破了我们对"重新开始"的信赖。我们总默认每一次调用都该是清白的、崭新的,像清晨第一杯水。可有些东西偏要替你记着,不声不响。

后来倒觉得这未必全是坏事。生活里有多少回,我们以为自己翻了篇,其实上一段还悄咪咪挂在后面。Python 只是老实,把这件事摊开给你看罢了。

你们有没有那种"我以为它忘了,它其实记得"的瞬间,不一定是代码里,也可能是别处?

caring
[链接]

这个坑我刚上手那阵也踩过,对着越来越长的列表发懵了好一会儿。后来就养成习惯了,默认参数但凡要放容器,一律先写 None,心里反倒踏实。

spicy23
[链接]

你这"小本本记你干过啥"的比喻实在离谱得好笑,画面感太强了。这陷阱最阴的一点就是你说的,它压根不吭声,默默把上次的结果当传家宝留着,等你想用它装新东西才发现缸里早有陈货。我当年也是被它整得怀疑人生,调半天才反应过来是同一个对象在反复上岗

grey81
[链接]

年轻的时候我也被这玩意儿坑过,不过不是列表,是个字典。当时写了个统计函数,默认参数塞了个 {},跑了几天发现数据越攒越怪,查到怀疑人生。后来才明白它那个默认对象是在函数定义那一下就生成了,之后每次调用都抱着同一个老伙计不撒手。怎么说呢

你帖子里说的改法是对的,写成 None 再里头判空最稳。我也见过有人图省事直接 lst=[] 然后开头来一句 lst = lst.copy(),虽然也能跑,但总感觉绕了道弯子,不如你那个干净。

这毛病其实不怪 python 温柔,是它太实在了,定义时建好就一直用,不跟你耍心眼。倒是咱们习惯了"每次都新的"那种直觉,反过来挨了顿揍。你们年轻人现在好,查资料方便,我那会儿连本像样的手册都难找,全靠自己撞墙撞明白的。

sharp_2003
[链接]

python这温柔一刀我早年也挨过,愣是以为内存没释放…,绕了好大弯才懂是默认参数在里头攒家底

prof_jr
[链接]

我也被这个整过。不过有一处值得再抠一下:你说"函数定义时就建好一次",在顶层函数上没问题,但如果这个 def 是包在另一个函数里的,外层每调一次,内层 def 就会重新执行,默认参数也跟着重建。所以严格讲,默认值是在 def 语句执行那一刻求值的,不是什么抽象的"定义时刻"。另外不止 list,dict、set 这些 mutable 对象都一样,本质是默认值被绑到了 function object 的 defaults 上。你现在那个 if lst is None 的改法最稳,没毛病。

euler__cat
[链接]

顺着你说的"建好一次"往下挖一层,那个默认列表其实不是每次被重新生成,而是 def 那一行执行时把 [] 求值了一遍,把得到的对象引用塞进了函数对象的 defaults 元组。之后每次调用只要没传实参,就是把同一个引用绑到 lst 上。你打印 add_item.defaults 就能直接看到那个被反复 append 的列表本体——它跟在函数外面写个全局变量 lst=[] 没本质区别,只是作用域藏进了函数里,所以才显得背后有小本本。

这个"共享可变默认值"的坑不止函数参数有,类属性也是重灾区。比如 class C: items=[],所有实例会共用同一个列表,c1.items.append 一动手,c2.items 也跟着变长。根子上同一回事:对象只创建一次,引用被多处共享。

我想给"温柔陷阱"翻个案:它不全是坑。有人故意拿它做函数内缓存,def lookup(k, _cache={}): … 第一次调用建好字典后续复用,单线程小脚本里挺省事。代价是这份状态挂在函数对象上、进程级共享、且非线程安全。所以与其叫陷阱,不如说它是把没套安全栓的刀,用得着时顺手,忘了它一直开着就容易割手。

你们平时靠 linter 拦这种写法,还是纯靠踩过一次就长记性?我反正现在一见默认参数写可变对象,手会先于脑子去改 ( ̄▽ ̄)

tender_2006
[链接]

我当初也是被这个坑过一回,还一度怀疑是自己逻辑写岔了。'背后有个小本本记你干过啥’这比喻太形象了,默认参数在定义时就绑定好确实反直觉,python这些温柔陷阱真会整人

euler_cat
[链接]

我头一次撞上这个也是一脸懵,盯着输出看了好久才反应过来。

顺着你说的补一句:它确实不是每次调用都新建默认参数,而是在 def 这个语句执行的时候就把那个 [] 造好、然后一直绑在同一个对象上。所以 list、dict、set 这些可变的默认参数全都会"攒"着,不止 [] 一种情况。eigentlich 这是有意设计的、文档里写明白的行为,不是什么藏着的 bug,跟"陷阱整人"的观感不太一样——它只是把求值时机摆在那儿,你不去看文档就会撞上。

顺带一提,正因为这种"共享"特性,早年还真有人拿可变默认参数当缓存使,比如 def f(x, _cache={}): 把结果塞进去下次直接用。不过现在 functools.lru_cache 是正道,那种写法连 linter 都会跳 warning,不太推荐了。

你后面那个 lst=None 再判断的写法,就是现在最稳的 idiom 了。话说你们还踩过哪些 Python 里这种"看着温柔其实有脾气"的地方?

voidism
[链接]

我前阵子也栽过这坑,排查半天才看清门道。默认参数绑死的是同一个对象,None那招最省事。现在我写函数压根不碰可变默认值了。

[首页] [上篇] 第 1 / 1 页 [下篇] [末页] [回复]
需要登录后才能回复。[去登录]
回复此帖进入修真世界