突变,重新绑定,复制值和赋值运算符之间的区别

时间:2012-01-31 03:29:26

标签: python arguments default-value

#!/usr/bin/env python3.2

def f1(a, l=[]):
    l.append(a)
    return(l)

print(f1(1))
print(f1(1))
print(f1(1))

def f2(a, b=1):
    b = b + 1
    return(a+b)

print(f2(1))
print(f2(1))
print(f2(1))

f1中,参数l具有默认值赋值,并且仅评估一次,因此三个print输出1,2和3.为什么{{1}不做类似的事情吗?

结论:

为了让我学到的东西更容易为这个帖子的未来读者导航,我总结如下:

  • 我在这个主题上找到了this很好的教程。

  • 我做了一些简单的example programs来比较变异重新绑定复制值之间的区别,以及< strong>赋值运算符。

4 个答案:

答案 0 :(得分:5)

因为在f2中,名称b会反弹,而在f1中,对象l会发生变异。

答案 1 :(得分:5)

relatively popular SO question详细介绍了这一点,但我会尝试在您的特定情况下解释该问题。


当您声明您的函数时,默认参数将在当时进行评估。每次调用该函数时都不会刷新。

您的函数表现不同的原因是因为您对它们的处理方式不同。在f1中,您正在改变对象,而在f2中,您正在创建一个新的整数对象并将其分配到b。你没有在这里修改b,你正在重新分配它。现在它是一个不同的对象。在f1中,您保留相同的对象。

考虑另一种功能:

def f3(a, l= []):
   l = l + [a]
   return l

此行为与f2类似,不会继续附加到默认列表。这是因为它创建了一个新的l而没有修改默认参数中的对象。


python中的常见样式是分配默认参数None,然后分配新列表。这解决了整个模糊性。

def f1(a, l = None):
   if l is None:
       l = []

   l.append(a)

   return l

答案 2 :(得分:3)

这是一个有点棘手的案例。当你很好地理解Python如何处理名称对象时,这是有道理的。如果你正在学习Python,你应该尽快发展这种理解,因为它绝对是你在Python中所做的一切的核心。

Python中的名称类似于af1b。它们仅存在于特定范围内(即,您不能在使用它的函数之外使用b)。在运行时,名称​​将引用到某个值,但可以随时使用赋值语句反弹到新值:

a = 5
b = a
a = 7

值在程序中的某个点创建,可以通过名称引用,也可以通过列表或其他数据结构中的槽来引用。在上面,名称a绑定到值5,然后反弹到值7.这对 5没有影响,无论如何总是值5许多名字目前都受其约束。

另一方面,b的分配会将名称b绑定到a 引用的值。之后重新绑定名称a 5没有影响,因此对名称b也没有影响,该名称也绑定到值5。

分配始终在Python中以这种方式工作。 永远不会对价值产生任何影响。 (除了某些对象包含“名称”;重新绑定这些名称显然会影响包含该名称的对象,但它不会影响更改之前或之后引用的名称的值)

每当您在赋值语句的左侧看到一个名称时,您就会(重新)绑定该名称。每当您在任何其他上下文中看到名称时,您将检索该名称引用的(当前)值。


有了这个,我们可以看到你的例子中发生了什么。

当Python执行一个函数 definition 时,它会计算用于默认参数的表达式,并将它们隐藏在某个地方。在此之后:

def f1(a, l=[]):
    l.append(a)
    return(l)

l不是任何内容,因为l只是函数f1范围内的名称,我们不在该函数内部。但是,值[]会存储在某处。

当Python执行转移到调用f1时,它会将所有参数名称(al)绑定到适当的值 - 值由调用者传入,或者在定义函数时创建的默认值。因此,当Python在执行调用f3(5)时,名称a将绑定到值5,名称l将绑定到我们的默认列表。

当Python执行l.append(a)时,看不到任何分配,所以我们指的是la的当前值。因此,如果要对l产生任何影响,它只能通过修改l引用的值来实现,事实上它确实如此。列表的append方法通过向末尾添加项来修改列表。因此,在此之后,我们的列表值仍然是存储为f1 的默认参数的相同值,现在已经附加了5(当前值为a)它,看起来像[5]

然后我们返回l。但是我们修改了默认列表,因此它会影响以后的任何调用。但是,我们返回默认列表,因此对我们返回的值的任何其他修改都将影响以后的任何调用!

现在,请考虑f2

def f2(a, b=1):
    b = b + 1
    return(a+b)

在这里,和以前一样,将值1加到某处作为b的默认值,当我们开始执行f2(5)时,名称a将被绑定到参数5,名称b将绑定到默认值1

但是我们执行赋值语句。 b出现在赋值语句的左侧,因此我们重新绑定名称b。首先Python编译b + 1,即6,然后将b绑定到该值。现在b绑定到值6. 但是函数的默认值没有受到影响:1仍然是1!


希望这可以解决问题。你真的需要能够根据引用值的名称进行思考,并且可以反弹以指向不同的值,以便理解Python。

这也许值得指出一个棘手的案例。我上面给出的规则(关于赋值始终绑定名称对值没有影响,因此如果其他任何影响名称,它必须通过更改值来实现)对于标准赋值是正确的,但并不总是“增强”赋值运算符例如+=-=*=

不幸的是,这些取决于你使用它们的内容。在:

x += y

这通常表现得像:

x = x + y

即。它计算一个新值,并将x重新绑定到该值,而不会影响旧值。但如果x是一个列表,那么它实际上会修改x引用的值!所以要小心那个案例。

答案 3 :(得分:0)

在f1中,您将值存储在数组中,或者更好地在Python中存储一个列表,其中在f2中您对传递的值进行操作。这是我对它的解释。我可能错了