python与传入参数

最近看到的文章中提到的一个神奇的现象。。。

python与其处理传入参数的方式

首先我们来看下面的程序:

1
2
3
4
5
6
def extend_list(val, l=[]):
l.append(val)
return l
list1 = extend_list(10)
list2 = extend_list('a')

这个list1和list2的输出是什么呢?
一般来说应该会认为是:

1
2
3
4
>>> list1
[10]
>>> list2
['a']

然而实际上答案却不是这样的。。。

1
2
3
4
>>> list1
[10, 'a']
>>> list2
[10, 'a']

会发现,list1指向的list在第二次extend_list执行的过程中,依然参与了函数的运算?这是为什么呢。。。

python的参数

对于python这类程序来说,变量不是[指针]而是[句柄],也就是说,python中的变量都只是一个指向内存位置的id。然而每次在定义一个函数的时候,可能是为了节约空间,所有的mutable对象都只会在定义的时候初始化一次,这就意味着,无论我们调用多少次这个函数,返回值中的list始终是同一个,所以当我们调用函数次数越多,list中的元素也就越多。

为了检验这一点,我们使用id()来检查这一点:

1
2
3
4
5
6
7
8
9
10
11
12
>>> def extend_list(val, l=[]):
l.append(val)
print(id(val))
print(id(l))
return l
>>> list1 = extend_list(10)
1774757232
61171016
>>> list2 = extend_list('a')
45571912
61171016

从这里可以看出,我们的val值对应的id始终是不一样的,而这个l始终指向了同一个内存地址。因此此时修改list1,list2或者再次调用函数,都将会对他们指向的那个list造成影响。

避免办法

这个问题官方是有提到的,官方不建议那mutable作为参数,取而代之的是:

1
2
3
4
5
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L

使用不可变参数None作为传入。

或者,我们可以在传入时产生一个副本(参考知乎大佬)

1
2
def foo(bar=[]):
bar = list(bar)

这样的话每次bar对应的都是另一个list,同样可以避免问题。