小心此坑:Python 函数参数的默认值是可变对象

开发 前端
Python 函数也是对象,参数的默认值就是对象的属性,在编译阶段参数的默认值就已经绑定到该函数,如果是可变对象,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 函数的实现可能会让你印象深刻,不过请注意,这样的用法非常危险,不可用于自己的代码中。

责任编辑:武晓燕 来源: Python七号
相关推荐

2010-09-28 10:23:36

SQL修改字段

2020-02-17 10:34:04

箭头函数开发 JavaScript

2012-08-01 09:50:11

交互设计UI设计

2023-12-14 10:23:01

C++模板函数

2021-02-25 13:40:17

MySQL数据库默认值

2024-04-08 07:58:11

Python数据类型字符串

2022-06-21 08:13:34

MySQL查询数据库

2010-11-23 16:49:42

MySQL设置当前时间

2010-07-15 10:37:15

SQL Server默

2010-10-22 16:56:35

sql server删

2009-12-24 16:03:16

ADO.NET部署

2010-09-28 10:35:58

SQL字段默认值

2010-09-07 16:05:23

SQL语句删除

2009-12-18 17:07:14

2010-09-28 15:24:43

sql语句

2010-09-03 10:52:10

SQL删除

2011-08-01 17:11:43

Objective-C 函数

2024-01-17 06:23:35

SwiftTypeScript定义函数

2010-10-22 15:36:57

2009-12-07 19:34:01

PHP函数可变参数列表
点赞
收藏

51CTO技术栈公众号