为什么使用`arg = None`修复Python的可变默认参数问题?

时间:2012-05-20 19:59:32

标签: python function arguments mutable

我正在学习Python,我正在处理the Mutable Default Argument problem

# BAD: if `a_list` is not passed in, the default will wrongly retain its contents between successive function calls
def bad_append(new_item, a_list=[]):
    a_list.append(new_item)
    return a_list

# GOOD: if `a_list` is not passed in, the default will always correctly be []
def good_append(new_item, a_list=None):
    if a_list is None:
        a_list = []
    a_list.append(new_item)
    return a_list

我了解a_list仅在首次遇到def语句时初始化,这就是bad_append的后续调用使用相同列表对象的原因。

我不明白为什么good_append有所不同。看起来a_list 只能初始化一次;因此,if语句只会在第一次调用函数时成立,这意味着a_list只会在第一次调用时重置为[],这意味着它仍然会累积所有过去的new_item {1}}价值观仍然存在问题。

为什么不呢?我错过了什么概念?每次a_list运行时good_append如何清除干净?

5 个答案:

答案 0 :(得分:22)

  

看起来a_list只会初始化一次

“初始化”不是Python中变量发生的事情,因为Python中的变量只是名称。 “初始化”只发生在对象上,它是通过类“__init__方法完成的。

当你写a = 0时,这是一项任务。这就是说“a应该引用表达式0”所描述的对象。它不是初始化; a可以在以后任何时候命名任何类型的任何其他内容,并且这是因为将其他内容分配给a而发生的。作业只是作业。第一个并不特别。

当您撰写def good_append(new_item, a_list=None)时,这不是“初始化”a_list。它正在设置对象的内部引用,即评估None的结果,以便在没有第二个参数的情况下调用good_append时,该对象会自动分配给a_list

  

意味着a_list只会在第一次调用时重置为[]

不,a_list[] a_list开始的任何时候都设置为None。也就是说,当None显式传递,或者省略参数时。

[]出现问题是因为在此上下文中表达式 []评估一次。编译函数时,会计算[],创建一个特定的列表对象 - 启动时恰好是空的 - 并且该对象用作默认值。

  

每次a_list运行时good_append如何清除?

没有。它不需要。

您知道问题是如何被描述为使用“可变默认参数”吗?

None不可变。

修改参数作为默认值的对象时会出现问题。

a_list = []不会修改之前引用的任何对象a_list。这不可以;任意对象不能神奇地就地转换为空列表。 a_list = []表示“a_list将停止引用之前提到的内容,并开始引用[]”。之前提到的对象保持不变。

当编译函数并且其中一个参数具有默认值时,该值 - 对象 - 将被烘焙到函数中(它本身也是一个对象!)。当您编写改变对象的代码时,该对象会发生变异。如果被引用的对象恰好是烘焙到函数中的对象,它仍然会发生变异。

但你不能改变None。这是不可改变的。

你可以改变[]。它是一个列表,列表是可变的。将项目附加到列表会改变列表。

答案 1 :(得分:15)

如果默认是可变的,None不是,则该问题才存在。与函数对象一起存储的内容是默认值。调用该函数时,将使用默认值初始化函数的上下文。

a_list = []

只需在当前函数调用的上下文中为名称a_list指定一个新对象。它不会以任何方式修改None

答案 2 :(得分:12)

默认值a_list(或其他任何默认值)在初始化后存储在函数内部,因此可以以任何方式进行修改:

>>> def f(x=[]): return x
...
>>> f.func_defaults
([],)
>>> f.func_defaults[0] is f()

因此func_defaults中的值与函数内部的函数相同(并在我的示例中返回以便从外部访问它。

IOW,调用f()时发生的情况是隐式x = f.func_defaults[0]。如果随后修改了该对象,您将保留该修改。

相比之下, 赋值函数始终是新的[]。任何修改都将持续到最后一次引用[]为止;在下一个函数调用中,创建一个新的[]

再说一遍,[]在每次执行时获取相同的对象并不是真的,但它(在默认参数的情况下)只执行一次然后保存。

答案 3 :(得分:4)

不,good_insert a_list中只有一次没有被宣传。

每次调用函数时都不指定a_list参数,使用默认值并使用并返回list的新实例,新列表不会替换默认值。

答案 4 :(得分:0)

python tutorial

默认值仅计算一次。

评估的(仅一次)默认值在内部存储(为简单起见,将其命名为x)。

案例[] 当您定义的函数的a_list默认为[]时,如果不提供a_list,则在为函数分配内部变量x 时, 。因此,当您追加到a_list时,实际上是在追加到x(因为a_listx现在引用相同的变量)。当您再次调用不带a_list的函数时,更新后的x将重新分配给a_list

案例NoneNone会被评估一次并存储在x中。如果您没有提供a_list,则变量x被分配给a_list。但是,您当然不会附加到x。您将一个空数组重新分配给a_list。此时xa_list是不同的变量。以同样的方式在不使用a_list的情况下再次调用该函数时,它首先从None获取值x,但随后又将a_list再次分配给空数组。

请注意,对于a_list = []情况,如果在调用函数时为a_list提供了显式值,则新参数不会覆盖{{1} },因为它只被评估一次。