我不知道f()
和g()
之间有什么区别。
在函数f
中,只要调用函数,列表L就会累积。
但是在功能g
中,不是。
def f(a, L=[]):
L.append([2]);
print(L);
def g(a, L=[]):
L = L+[2];
print(L);
print("f:")
f(1) # [[2]]
f(2) # [[2], [2]]
f(3) # [[2], [2], [2]]
print("g:")
g(1) # [2]
g(2) # [2]
g(3) # [2]
答案 0 :(得分:9)
您在示例中看到的奇怪行为并不是由于+运算符和append函数之间的差异。
这是由于您为功能参数分配了一个列表作为默认值,并在g
的局部变量中分配了串联的结果。
那不是很广为人知,但是将列表(或字典或对象)定义为参数默认值可能不是您想要的。这样做时,将在所有函数调用中共享用作参数默认值的列表。
因此,对于f
函数:
L
参数的情况下首次调用它,将采用默认值([]
)并附加2
。变成[2]
L
参数,则使用默认值,但现在是[2]
。再次附加2
,它变成[2, 2]
,它现在是函数的默认值。用于g
函数:
f
函数,默认值是一个空列表,但是此列表永远不会被修改。实际上,L + [2]
的结果会受到局部变量L
的影响,该函数的默认值保持不变。因此,当您再次调用该函数时,默认值始终为[]
。+=
运算符代替串联,而赋值实际上会修改L
参数的默认值。如果这样做,g
函数的行为将类似于f
函数。结论是,使用空列表作为参数默认值几乎绝不是您想要的。相反,您可能想要这样:
def f(a, L=None):
L = L or []
...
答案 1 :(得分:3)
在第一个函数中,当您将新值附加到列表末尾时,将更新现有的list
。
在第二个函数中,您只是将值重新分配给列表,这意味着您没有在列表中添加值,而是说,每次调用该函数时,都使用相同的值重新创建它。
在两种情况下,您都将为函数创建局部变量,该变量附加到该函数clouser
上。因此,在两种情况下,您都有局部变量,它们位于函数范围内。第一次运行函数时,将记住变量的默认值,然后再执行一次函数时,它将不会被函数重新初始化,而是从函数范围中获取。< / p>
例如,尝试将g
函数的代码更新为下一个:
def g(a, L=[]):
L += [2]
print(L)
您将看到,L
也像f
函数中一样累积,因为在这里您无需重新分配值,而是更新现有变量。
答案 2 :(得分:2)
Append将元素添加到列表的末尾。
因此,如果列表最初是L = [],那么L.append([2])
会将单个元素列表附加到原始列表L。如果再次执行L.append([3])
,它将列表将另一个元素列表附加到末尾原始列表L。您的列表L现在实际上是列表列表。
+
是串联运算符。它像连接两个字符串一样连接两个列表。
此外,请记住,+运算符将始终返回包含已添加元素的新列表。
这就是使用+运算符时列表不增长的原因。如果您只想更改现有列表,则可以使用扩展功能。以下答案和评论将使您更清楚:
Difference between append vs. extend list methods in Python
希望有帮助。
编辑:为显示+每次如何返回新列表,以下是修改后的代码,该代码在每次打印列表时都会打印列表的ID:
def f(a, L=[]):
L.append([2]);
print(L);
print(id(L))
def g(a, L=[]):
#The following statement will print the id of default L
print(id(L))
L = L+[2];
#The following statement will print the id of a new L stored in a diff location. The default L isnt modified.
print(L);
print(id(L))
f:
[[2]]
2354851792712
[[2], [2]]
2354851792712
[[2], [2], [2]]
2354851792712
g:
2354851792968
[2]
2354851791944
2354851792968
[2]
2354851791816
2354851792968
[2]
2354851792328
如上所述,每次添加时,都使用相同的默认列表(2354851792712)。因此,您的清单在增加。另一方面,使用+返回新列表并将其存储在新位置,而不是修改默认的L位置(2354851792968)。因此,它不会增长。
答案 3 :(得分:1)
您正在使用可变的默认值。首次调用f()
或g()
时,解释器会创建一个持久列表。使用append
中的f()
来修改此列表。 +
中的g()
运算符会导致创建新的列表对象,因此,为默认值创建的持久列表对象保持不变。使用id(L)
打印对对象的引用以获得更深入的了解。
使用可变的默认值被认为是一种反模式,因为它们会导致这种意外的行为(Source)。
尝试运行此代码,您将在分配+
操作的结果后看到L具有新的ID,并且不再“指向”持久性默认值对象(保持不变)。
def a(new_element, L=[]):
print(id(L))
L = L + [new_element]
print(id(L))
a(1)
答案 4 :(得分:1)
list.append(element)
将“元素”添加到列表中。不会根据“元素”的数据类型进行区分。
级联运算符'+'似乎执行相同的操作,但仅级联相同的数据类型。因此,您只能将列表连接到列表。您不能拥有list1+"string"
。但是您可以拥有list1+["string1"]
,list1.append("string1")
和list1.append(["string1"])
。
与通常的看法相反,在python中,列表不是不可变的(可变的)。这意味着可以在不更改其引用的情况下一遍又一遍地编辑同一对象。但是,元组和字符串是不可变的。这意味着,无论何时编辑元组或字符串,都会创建一个新对象。然后,旧的字符串/元组被垃圾收集系统丢弃。
答案 5 :(得分:1)
这很好地说明了您问题的第一部分: https://docs.quantifiedcode.com/python-anti-patterns/correctness/mutable_default_value_as_argument.html
因此,由于您具有可变的默认参数,因此python会将其创建为一个,并将其传递给每个新的函数调用。
这说明了功能f的行为。对于函数g,您必须考虑+运算符会创建一个新列表,该列表将绑定到名称L
。原始的默认列表将传递给每个函数调用,但是不会被触及(在函数f中,您将更改对象本身)。
编辑:找到此链接:http://effbot.org/zone/default-values.htm
这很好地解释了该行为。
因此def是可执行文件,这就是为什么在函数定义中计算默认参数的原因。然后,默认参数存储在function.func_defaults
中。在函数调用期间,默认参数从function.func_defaults
传递到您的本地名称L
。