#!/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>赋值运算符。
答案 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中的名称类似于a
,f1
,b
。它们仅存在于特定范围内(即,您不能在使用它的函数之外使用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
时,它会将所有参数名称(a
和l
)绑定到适当的值 - 值由调用者传入,或者在定义函数时创建的默认值。因此,当Python在执行调用f3(5)
时,名称a
将绑定到值5,名称l
将绑定到我们的默认列表。
当Python执行l.append(a)
时,看不到任何分配,所以我们指的是l
和a
的当前值。因此,如果要对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中您对传递的值进行操作。这是我对它的解释。我可能错了