函数foo
的预期目标是将作为参数提供的数字添加到列表中,如果为 0,则重置列表。首先我写了这个程序:
def foo(n, bar = []):
if n == 0:
bar = []
print("list empty")
else:
bar.append(n)
for y in bar:
print(y, end=', ')
print()
foo(5)
foo(3)
foo(0)
foo(6)
输出:
5,
5, 3,
list empty
5, 3, 6,
但看起来bar = []
被忽略了。然后我用bar.clear()
更改了bar = []
,它的工作原理就像我想的那样:
def foo(n, bar = []):
if n == 0:
bar.clear()
print("list empty")
else:
bar.append(n)
for y in bar:
print(y, end=', ')
print()
foo(5)
foo(3)
foo(0)
foo(6)
输出:
5,
5, 3,
list empty
6,
我不明白为什么bar.clear()
的工作方式与bar = []
不同,因为clear()
应该
从集合中删除所有元素。
所以bar = []
也做了同样的事情。
编辑:我不认为我的问题是"最不惊讶"和可变默认参数的重复,我知道
默认值仅计算一次。
(来自官方教程(但我问的是,为什么bar = []
不编辑(在本例中为清除(列表,而附加和清除可以?
bar = []
重新绑定bar
;它创建一个全新的空list
,并将名称bar
绑定到它上面。以前是什么bar
并不重要;它本来可以是一个int
,一个tuple
,或者dict
,现在它已经无关紧要了,因为它现在绑定到一个全新的list
。旧绑定不再存在于当前方法调用中,但原始list
作为默认参数的值仍然存在foo
(注意:由于这种混淆,可变默认值被认为是令人惊讶和丑陋的(。
bar.clear()
在现有list
上调用一个方法,它告诉list
清除自己(并且对bar
所指的内容没有影响;它最好是具有clear
方法的东西,但除此之外,它实际上并不需要它是一个list
(。由于它与您开始使用的bar
相同(引用函数默认值中缓存的相同可变默认值(,这会影响您将来的调用(因为bar
继续引用您提供的list
作为可变默认值(。
你真正应该理解的是变量在Python中是如何工作的。
变量始终只是对实际内容的引用。变量本身不存储任何内容,它只指向未命名的内容。
现在,当你调用foo()
时,你所拥有的是内存中的一个列表,局部bar
变量指向它。如果您调用bar.clear()
,该列表实际上已被清除,这就是您想要的。但是,如果您说bar = []
您只需将局部bar
变量绑定到一个新的空列表,另一个列表(已经包含 5 和 3(保持不变,下次调用foo()
时将使用它。
在代码中,bar
是对内存中某个列表的引用。 使用bar = []
时,变量bar
与新的(空(列表相关联。使用bar.clear()
时,可以修改当前列表。
一般来说,这意味着 Python 中的列表是通过引用而不是按值传递的。您可以在此处阅读有关它的更多信息。
当你做一个list.clear()
时,列表会被清除:-( 当您执行list = []
时,该列表将被一个新的空列表覆盖。
不同之处在于,如果某些内容在 if 被覆盖之前引用了该列表,它仍将引用您认为现在已被清除的值。
这是引入极难调试错误的绝佳方法。
假设您有一个包含一些值的列表,并将其分配给名为 foo 的变量。
foo = [0, 1]
此变量现在指向内存中的此列表。
假设您随后创建了一个名为 bar 的新变量并分配给 foo。
bar = foo
这两个变量现在指向内存中的同一列表对象。如果你然后改变foo并让它相等[],酒吧会发生什么?
foo = []
bar == [0, 1] # True
现在让我们用 foo.clear(( 做同样的事情。
foo.clear()
bar == [] # True
这是因为在列表上使用 clear 方法会清除该列表对象,这会影响引用此对象的所有内容,而将变量分配给空列表只会更改该变量的内容,而不会更改原始列表。
它具有所需的属性:
def foo(n, bar = []):
if n == 0:
bar[:] = []
print("list empty")
else:
bar.append(n)
for y in bar:
print(y, end=', ')
print()