pin_drop当前位置:知识文库 ❯ 图文
Python这个“魔鬼细节”让我昨晚差点砸电脑!列表默认参数坑哭你,一招搞定!
哎,先叹口气。昨天本来想着下班前优化一个小功能,结果硬生生搞到凌晨两点,最后发现居然是一个我早就知道、但一不留神又踩进去的坑!气得我差点把桌上的咖啡泼屏幕上——幸好那是最后一杯。
事情是这样的:我在写一个函数,用来记录用户的操作日志,每次调用就往列表里加一条记录。我寻思着,为了灵活,让调用方可以自己传一个列表进来,如果没传就用默认的空列表。于是乎,我优雅地写出了下面这段代码:
代码示例
def add_log(message, log_list=[]):
log_list.append(message)
return log_list
是不是看起来人畜无害?我当时也是这么想的。然后我模拟了几个用户操作:
代码示例
user1_logs = add_log('登录')
print(user1_logs) # 输出:['登录']
user2_logs = add_log('浏览页面')
print(user2_logs) # 期待:['浏览页面'],但实际输出:['登录', '浏览页面']
哎?user2的日志怎么把user1的也带上了?我当时就懵了,难道Python偷偷开了共享内存?再试一次:
代码示例
print(add_log('下单')) # 输出:['登录', '浏览页面', '下单']
好家伙,这列表像吃了炫迈一样,根本停不下来!我明明没传参数,它居然把之前所有调用攒下的记录都背在身上。这要是线上环境,日志直接乱套,背锅都找不到方向。
其实这个问题,但凡有点Python经验的都知道——可变默认参数陷阱。但知道归知道,熬夜写代码脑子一热就容易忘。今天必须把这个坑给它填平,顺便把"肇事司机"拉出来游街示众。
为啥会出现这种情况?
Python函数的默认值只在函数定义的时候被计算一次,而且这个默认值会一直存在,不会每次调用都重新创建。如果默认值是可变对象(比如列表、字典、集合),那它就像个"幽灵",在你每次调用函数时默默地跟着你。
当你修改这个默认对象时,实际上修改的是那个一直存在的"幽灵",所以下一次调用它的时候,它已经是被改过的状态了。就像我上面的例子,第一次调用在默认列表里加了"登录",第二次调用还是那个列表,所以"浏览页面"就被追加进去了。
怎么解决?简单粗暴!
既然默认列表是"幽灵",那我们就别给它留肉身。用None作为默认值,然后在函数内部创建新列表。这样每次调用,如果没有传列表,都会得到一个崭新的列表。
看改造后的代码:
代码示例
def add_log(message, log_list=None):
if log_list is None:
log_list = []
log_list.append(message)
return log_list
再试试:
代码示例
print(add_log('登录')) # ['登录']
print(add_log('浏览页面')) # ['浏览页面']
print(add_log('下单')) # ['下单']
完美!各用各的,谁也不干扰谁。这就像每次来人,都给你新拿一个一次性纸杯,而不是用别人喝过的。
还有哪些类似的坑?
除了列表,字典、集合也是一样的道理。比如:
代码示例
def add_user(user, user_dict={}):
user_dict[user] = True
return user_dict
同样会累积。还有类属性、函数内部定义的函数如果引用了外层可变变量,也可能出现类似问题。总之,只要默认值是可变对象,就要多留个心眼。
提示:记住一个原则——永远不要用可变对象(列表、字典、集合等)作为函数的默认参数。如果需要一个空的默认值,使用None然后在函数内部创建新对象。
一点感慨
其实很多编程语言里都有这种"细节魔鬼",C++的未定义行为、JavaScript的闭包陷阱、PHP的引用……踩坑不可怕,可怕的是踩完不总结,下次继续踩。我昨晚就是典型的"脑子一抽"。所以今天我决定把这个坑写下来,既给自己立个flag,也帮兄弟们避雷。
以后写默认参数,先问问自己:这玩意儿是可变的吗?如果是,果断None走起。如果你也遇到过类似问题,欢迎评论区吐槽,让大家看看你踩过的坑,咱们一起乐呵乐呵,哦不,一起进步。
对了,如果你是刚学Python,千万别被这个吓到。Python大部分时候都很可爱,只是偶尔会皮一下。记住这个套路,以后遇到类似情况,心里就有数了。
最后,如果你觉得这篇文章有用,点个赞或者收藏一下,下次写代码前翻出来看看,保你不再被列表默认参数坑哭。毕竟,程序员何苦为难程序员?都是熬夜的兄弟!
常见问题
为什么Python的默认参数只计算一次?
Python在函数定义时就计算默认值,并将其保存在函数对象的__defaults__属性中。这种设计可以提高性能,避免每次调用都重新创建默认对象。但对于可变对象,这会导致所有调用共享同一个对象。
哪些对象是可变对象,哪些是不可变对象?
常见可变对象:列表(list)、字典(dict)、集合(set)。常见不可变对象:整数(int)、浮点数(float)、字符串(str)、元组(tuple)、None。只有可变对象作为默认参数时才会出现这个问题。
如何查看函数的默认参数?
可以通过函数对象的__defaults__属性查看。例如:add_log.__defaults__会显示默认参数的当前值,你可以看到列表在多次调用后的状态变化。
其他编程语言也有这个问题吗?
不是的。很多语言(如Java、C++)在每次函数调用时都会重新计算默认值。Python的这种设计是其独特之处,了解后就能避免踩坑。
本文涉及AI创作
内容由AI创作,请仔细甄别list快速访问
poll相关推荐
Python应用领域
Python发展历史
Python简介
PHP数组去重:为什么array_unique会坑你?两种解决方案