python中“ append”和“ +”之间有什么区别?

时间:2018-08-10 07:09:57

标签: python

我不知道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]

6 个答案:

答案 0 :(得分:9)

您在示例中看到的奇怪行为并不是由于+运算符和append函数之间的差异。

这是由于您为功能参数分配了一个列表作为默认值,并在g的局部变量中分配了串联的结果。


那不是很广为人知,但是将列表(或字典或对象)定义为参数默认值可能不是您想要的。这样做时,将在所有函数调用中共享用作参数默认值的列表。

因此,对于f函数:

  1. 您在没有L参数的情况下首次调用它,将采用默认值([])并附加2。变成[2]
  2. 您第二次调用它而没有使用L参数,则使用默认值,但现在是[2]。再次附加2,它变成[2, 2],它现在是函数的默认值。

用于g函数:

  • 对于f函数,默认值是一个空列表,但是此列表永远不会被修改。实际上,L + [2]的结果会受到局部变量L的影响,该函数的默认值保持不变。因此,当您再次调用该函数时,默认值始终为[]
  • Klimenkomud said in his answer一样,使用+=运算符代替串联,而赋值实际上会修改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