35

我正在学习 Python,我正在处理Mutable Default Argument 问题

# 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时才会初始化,这就是为什么后续调用使用相同的列表对象。defbad_append

我不明白的是为什么good_append工作方式有所不同。看起来a_list仍然只会初始化一次;因此,该if语句仅在第一次调用该函数时为真,这意味着a_list只会[]在第一次调用时重置,这意味着它仍然会累积所有过去的new_item值并且仍然是错误的。

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

4

5 回答 5

30

看起来 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_listNoneNone

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

a_list每次运行时如何擦干净good_append

它没有。它不需要。

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

None是不可变的。

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

a_list = []不会修改a_list之前提到的任何对象。这不可以; 任意对象不能神奇地就地转换为空列表。a_list = []意思是“a_list应该停止引用它之前引用的内容,并开始引用[]”。先前引用的对象未更改。

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

但是你不能变异None。它是不可变的。

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

于 2012-05-20T20:59:44.047 回答
19

的默认值a_list(或任何其他默认值)一旦被初始化就会存储在函数的内部,因此可以以任何方式修改:

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

分别 对于 Python 3:

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

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

换句话说,当调用f()是隐式时会发生什么x = f.func_defaults[0]。如果该对象随后被修改,您将保留该修改。

相反,函数内部的赋值总是得到一个新的[]. 任何修改都将持续到最后一次引用该修改为止[];在下一个函数调用中,[]会创建一个新函数。

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

于 2012-05-20T20:42:12.840 回答
17

仅当默认是可变的时才存在问题,而事实None并非如此。与函数对象一起存储的是默认值。当函数被调用时,函数的上下文被初始化为默认值。

a_list = []

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

于 2012-05-20T20:03:19.213 回答
4

不, ingood_insert a_list不会只初始化一次。

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

于 2012-05-20T20:36:07.817 回答
0

python教程

默认值只计算一次。

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

case []: 当你定义函数时a_list默认为[],如果你不提供a_list它会被赋值为内部变量xwhen 。因此,当您追加到 时a_list,您实际上是追加到x(因为a_listx现在引用相同的变量)。当您在没有 的情况下再次调用该函数时a_list,更新x的内容将重新分配给a_list

case None 该值None被评估一次并存储在x. 如果您不提供 ,a_list则将变量 x 分配给 a_list。但你当然不会追加x。您将一个空数组重新分配给a_list. 此时xa_list是不同的变量。同样,当您再次调用该函数而不使用 时a_list,它首先从中获取值Nonex然后将 a_list 再次分配给一个空数组。

请注意,对于这种a_list = []情况,如果您在调用函数时提供显式值a_list,则新参数不会覆盖x,因为它只计算一次。

于 2020-05-11T20:29:40.737 回答