我是一个python初学者,阅读'python tutorial',它说如果我们有一个函数:
def f(a, L=[]):
L.append(a)
return L
print f(1)
print f(2)
print f(3)
这将打印
[1]
[1, 2]
[1, 2, 3]
因为默认值只计算一次而list是可变对象。我能理解。
它说继续,如果我们不希望在后续调用之间共享默认值,我们可以:
def f(a, L=None):
if L is None: #line 2
L = []
L.append(a)
return L
print f(1)
print f(2)
print f(3)
这将输出:
[1]
[2]
[3]
但为什么?怎么解释这个。我们知道默认值仅被评估once
,当我们调用f(2)时,L不是None而if
(第2行)不能为真,所以L.append(a) == [1,2]。我可以猜一下原因再次评估默认值,但是'某种原因'是因为python解释器看到if L is None: L = []
答案 0 :(得分:17)
Python通过值将参数传递给函数;因此,对于对象,传递的值是对象的引用,而不是对象的新副本。
这与official docs的以下部分一起帮助我更好地理解它(强调我的):
执行函数定义时,将评估默认参数值。 这意味着在定义函数时,表达式将被计算一次,并且每次调用都会使用相同的“预先计算”值。这对于理解默认参数何时是一个特别重要可变对象,例如列表或字典:如果函数修改对象(例如,通过将项附加到列表),则默认值实际上被修改。 [...]解决这个问题的方法是使用None作为默认值,并在函数体中明确地测试它[...]
全部放在一起:
如果您将参数的默认值定义为可变对象(例如[]
),那么"预先计算" value是该对象的引用,因此对该函数的每次调用将始终引用同一个对象,然后可以在该函数的多个调用中进行变异。
但是,由于None
是一个不可变的内置类型,因此"预先计算了"默认值None
的值就是这样。因此,每次调用函数时参数都为None
。
希望这有帮助!我确实认为教程可能有更好的措辞,因为我最初也对此感到困惑。
答案 1 :(得分:14)
“默认值仅评估一次”并不意味着具有默认值的参数在函数的调用之间保留其值。这意味着您指定的表达式(None
部分的def f(a, L=None)
部分)将被评估一次,并且它产生的对象将存储在隐藏位置并在该参数的值没有的情况下重新使用随叫随到。在每次调用时,参数仍会重置为值(默认值或不是默认值)。
答案 2 :(得分:3)
在第二个示例中,您有一个变量L
。首先L
指的是None
。您将其重新指定到每个调用的新空列表,然后改变该新列表。请注意,L = []
与L = list()
但是,在第一个示例中,L在函数声明处设置为新列表一次。在每次调用函数时,L都不会重置为[]
。所以你总是在改变同一个列表。
答案 3 :(得分:3)
我认为这种情况正在发生,因为列表是一个可变对象,而值None是不可变的。
对于第一个函数,变量L在函数环境之外(在函数定义中),它引用一个空列表。然后在函数环境中对此列表进行更改,但由于列表是可变的,因此函数环境之外的变量L引用此现在已变异的列表,并且每次调用函数时都会传播更改。
对于第二个函数,变量L也在函数环境之外(在函数定义中),但这次它引用None,它是不可变的。现在,您在函数环境中所做的每个更改都不会影响L在函数环境之外引用的内容。函数环境中的变量L在您更改它时会引用不同的变量。首先,它引用一个空列表,然后是一个附加值的列表。然后返回此列表。下次调用该函数时,可以使用函数环境之外的变量L调用它,该变量L没有更改,仍然引用None。
希望这是有道理的。
答案 4 :(得分:1)
发生的情况如下:
当调用python函数时,它会在定义的环境中进行评估,而不是在调用的环境中进行评估,尽管第二部分是次要的(没有双关语)是为了回答你的问题。
默认参数仅在函数定义时计算一次。这会创建一个闭包。将闭包视为函数代码+定义函数的环境。
因此,在这种情况下定义函数时,L
已分配给[]
,现在对该函数的每次后续调用都将使用此L
的值。
该教程还提到:
在定义范围中的函数定义点评估默认值(并且定义范围是闭包的一部分以及函数代码)
http://docs.python.org/2/tutorial/controlflow.html#default-argument-values