提问人:75th Trombone 提问时间:5/21/2012 最后编辑:smci75th Trombone 更新时间:12/21/2021 访问量:7841
为什么使用“arg=None”可以解决 Python 的可变默认参数问题?
Why does using `arg=None` fix Python's mutable default argument issue?
问:
我正在学习 Python,我正在处理可变默认参数问题。
# 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
我不明白的是为什么工作有什么不同。看起来仍然只初始化一次;因此,该语句仅在第一次调用函数时为 true,这意味着只会在第一次调用时重置为,这意味着它仍然会累积所有过去的值并且仍然有问题。good_append
a_list
if
a_list
[]
new_item
为什么不呢?我错过了什么概念?每次运行时如何擦拭干净?a_list
good_append
答:
仅当默认值是可变的,而默认值不是可变时,才存在问题。与函数对象一起存储的是默认值。调用函数时,将使用默认值初始化函数的上下文。None
a_list = []
只需在当前函数调用的上下文中为名称分配一个新对象。它不会以任何方式修改。a_list
None
评论
a_list = None
不,in 不是只启动一次。good_insert
a_list
每次调用函数而不指定参数时,都会使用默认值,并使用并返回新的实例 ,新列表不会替换默认值。a_list
list
的默认值(或任何其他默认值)在初始化后存储在函数的内部,因此可以以任何方式进行修改: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]
相反,函数内部的赋值总是得到一个新的 .任何修改都将持续到最后一次引用消失为止;在下一个函数调用中,将创建一个新的函数。[]
[]
[]
再次,在每次执行时都获得相同的对象是不正确的,但它(在默认参数的情况下)只执行一次然后保留。[]
评论
f()
x = f.func_defaults[0]
x=[]
f.__defaults__[0] = []
x = f.__defaults__[0]
看起来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)。x
案例 []
:当您定义函数时,默认为 ,如果不提供 ,则在 时为其分配内部变量 x
。因此,当你追加到 时,你实际上是在追加 (因为 和 现在引用同一个变量)。当您再次调用没有 的函数时,更新的函数将重新分配给 。a_list
[]
a_list
a_list
x
a_list
x
a_list
x
a_list
案例 无
:该值计算一次并存储在 中。如果未提供 ,则将变量 x 分配给 a_list。但你当然不会附加。将空数组重新分配给 。在这一点上,是不同的变量。同样,当您再次调用 without 函数时,它首先从中获取值,然后a_list再次分配给空数组。None
x
a_list
x
a_list
x
a_list
a_list
None
x
请注意,在这种情况下,如果在调用函数时提供显式值,则新参数不会重写,因为该参数仅计算一次。a_list = []
a_list
x
上一个:不可变容器中的可变类型
下一个:整数是不可变的吗
评论