Strange behavior with a list as a default function argument
Possible Duplicate:
"Least Astonishment" in Python: The Mutable Default Argument
List extending strange behaviour
Pyramid traversal view lookup using method names
假设我有这个功能:
1 2 3
| def a(b=[]):
b += [1]
print b |
调用它会产生以下结果:
1 2 3 4 5 6
| >>> a()
[1]
>>> a()
[1, 1]
>>> a()
[1, 1, 1] |
当我把b += [1]改为b = b + [1]时,函数的行为改变:
1 2 3 4 5 6
| >>> a()
[1]
>>> a()
[1]
>>> a()
[1] |
b = b + [1]与b += [1]有何区别?为什么会这样?
- 您在函数范围内绑定b。使用EDOCX1[1]并不能做到这一点…
- 而list += sequence与list = list + list不同…
在python中,不能保证a += b与a = a + b执行相同的操作。
对于列表,someList += otherList在适当位置修改someList,基本相当于someList.extend(otherList),然后将名称someList重新绑定到同一列表。另一方面,someList = someList + otherList通过连接两个列表来构造一个新列表,并将名称someList绑定到该新列表。
这意味着,对于+=,名称最终指向它已经指向的同一个对象,而对于+,它指向一个新对象。由于函数默认值只计算一次(见这一引人深思的问题),这意味着使用+=操作会堆积起来,因为它们都修改了相同的原始对象(默认参数)。
- >然后将名称someList重新绑定到同一个列表中。我不认为这是真的。它不会重新绑定,只是更改调用其extend方法的列表。
- @沃鲁克:是真的。您可以看到,如果分配是针对某个属性的(例如,使其__setattr__打印出某个对象,执行obj.someProp=[1],然后执行obj.someProp += [2]和watch __setattr__被调用)。这里也可以看到。
- 事实上,列表的+=和+是两种不同的实现:列表扩展奇怪的行为
- 嗯,你说得对。我不明白为什么,直到我发现,obj.a_list += another_list等同于obj.a_list = operator.iadd(obj.a_list, another_list)而不是obj.a_list.extend(another_list)。
b += [1]改变了函数默认值(导致最不令人惊讶的FAQ)。b = b + [1]采用默认参数b—用+ [1]创建一个新的列表,并将其绑定到b上。一个改变列表,另一个创建新列表。
定义函数时
1 2 3
| >>> def a(b=[]):
b += [1]
return b |
它将所有默认参数保存在一个特殊的位置。它可以通过以下方式进行实际访问:
1 2
| >>> a.func_defaults
([],) |
第一个默认值是一个list具有以下ID:
1 2
| >>> id(a.func_defaults[0])
15182184 |
让我们尝试调用函数:
1 2 3 4
| >>> print a()
[1]
>>> print a()
[1, 1] |
并查看返回值的ID:
1 2 3 4
| >>> print id(a())
15182184
>>> print id(a())
15182184 |
如您所见,它与第一个默认值列表的ID相同。
该函数的不同输出是由b+=...修改b的位置而不是创建新的列表这一事实来解释的。而b是保存在缺省值元组中的列表。因此,对列表的所有更改都保存在那里,并且函数的每次调用都使用不同的b值。