小心此坑:Python 函数参数的默认值是可变对象
看到了有给Python函数参数的默认值传递可变对象,以此来加快斐波那契函数的递归速度,代码如下:deffib(n,cache={0:0,1:1}):ifnnotincache:cache[n]=fib(n-1)+fib(n-2)returncache[n]是不是很新奇,居然可以这样,速度真的非常快,运行结果如下:不过,我劝你不要这样做,而且IDE也会提示你这样做很不好:这是因为,万物皆对象,Python函数也是对象,参数的默认值就是对象的属性,在编
看到了有给 Python 函数参数的默认值传递可变对象,以此来加快斐波那契函数的递归速度,代码如下:
def fib(n, cache={0: 0, 1: 1}):
if n not in cache:
cache[n] = fib(n - 1) + fib(n - 2)
return cache[n]是不是很新奇,居然可以这样,速度真的非常快,运行结果如下:

不过,我劝你不要这样做,而且 IDE 也会提示你这样做很不好:

这是因为,万物皆对象,Python 函数也是对象,参数的默认值就是对象的属性,在编译阶段参数的默认值就已经绑定到该函数,如果是可变对象,Python 函数参数的默认值在会被存储,并被所有的调用者共享,也就是说,一个函数的参数默认值如果是一个可变对象,例如 List、Dict,调用者 A 修改了它,那么之后调用者 B 在调用的时候看到的就是 A 修改后的结果,这样的模式往往会产生意想不到的结果,比如上面 fib 的算法,但更多的是 bug。
可以看下这段简单的代码:
def func(n, li = []): for i in range(n): li.append(i) print(l) func(2) # [0,1] func(3,l=[1,2]) # [1,2,0,1,2] func(2) # [0,1]
你可以先估算一下这段代码的输出,如果和注释中的一样,那你就错了。正确的结果是:
[0, 1] [1, 2, 0, 1, 2] [0, 1, 0, 1]
你可能会觉得,最后一个 func(2) 怎么是这样,不急,我们 print(id(li)) 调试一下:
def func(n, li = []): print(id(li)) for i in range(n): li.append(i) print(li) func(2) func(3,li=[1,2]) func(2)
结果如下:
140670243756736 [0, 1] 140670265684928 [1, 2, 0, 1, 2] 140670243756736 [0, 1, 0, 1]
有没有发现,第一个 func(2) 和第二个 func(2) 的 id 是一样的,说明它们用到的是 li 是同一个,这就参数的默认值是可变对象的逻辑,对于所有的调用者来讲,是共享的。
如果要深入研究 Python 为什么这么设计,可以移步 http://cenalulu.github.io/python/default-mutable-arguments/
如何避免?
最好的方式是不要使用可变对象作为函数默认值。如果非要这么用的话,下面是一种解决方案:
def generate_new_list_with(my_list=None, element=None): if my_list is None: my_list = [] my_list.append(element) return my_list
这样,如果 my_list 默认值永远都是 []。
最后
我想那个 fib 函数的实现可能会让你印象深刻,不过请注意,这样的用法非常危险,不可用于自己的代码中。
Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。
极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。
















