0

可能的重复:
Python 中的“Least Astonishment”:可变默认参数

考虑以下两个函数

def a(var=[0]):
    print (var)
    var = var + [4]

def b(var=[0]):
    print (var)
    var.append(4)

它们的行为应该相同,但更重要的是,如果不带参数调用,两者都应该简单地打印“[0]”。行为非常不同,只有 a() 会一直打印 '[0]'。

>>> a()
[0]
>>> a()
[0]
>>> a()
[0]

>>> b()
[0]
>>> b()
[0, 4]
>>> b()
[0, 4, 4]

为什么 a() 与 b() 的功能不同?

如果您希望在没有参数传递给函数的情况下使用默认值覆盖变量,那么您似乎无法使用列表 API。如果你这样做,该函数将“记住”变量以前是什么。

我的代码中的情况出现在递归函数中,因此仅“删除”不需要的变量并不会真正起作用。每次调用不带参数的函数时,有没有办法覆盖变量?

经过数小时的研究,我发现了这一点。它可能与上述问题有关,并可能导致答案。我们可以像这样定义一个生命周期类:

class lifetime:   # adapted from http://naml.us/blog/tag/variable-lifetime
    def __init__(self, name):
        self.name = name
        print ('creating: ' + name)
    def __del__(self):
        print ('destroying: ' + self.name)
    def __iadd__(self, a):
        self.append(a)
    def append(self, a):
        self.name += ' and ' + a.name
        print('appending: ' + a.name + ' to ' + self.name)

然后定义2个函数:

def a(var=lifetime('a')):
    print (var)
    var += life('appendage')

def b(var=lifetime('b')):
    print (var)
    var.append(life('appendage'))

>>> a()
<__main__.lifetime object at 0x00000000031FFA90>
creating: appendage
appending: appendage to a and appendage
destroying: appendage
>>> a()
<__main__.lifetime object at 0x00000000031FFA90>
creating: appendage
appending: appendage to a and appendage and appendage
destroying: appendage
>>> b()
<__main__.lifetime object at 0x00000000031FFB38>
creating: appendage
appending: appendage to b and appendage
destroying: appendage
>>> b()
<__main__.lifetime object at 0x00000000031FFB38>
creating: appendage
appending: appendage to b and appendage and appendage
destroying: appendage

似乎它只是对参数的默认值进行一次评估,然后使用该评估的任何值。它从不说“正在创建:a”或“正在创建:b”。所以也许如果每次都评估默认参数,它会导致对实际问题的回答。

4

5 回答 5

2

正如您所说,这些默认值仅在声明函数时评估一次。这就是为什么将列表或散列文字分配为默认值绝不是一个好主意,因为您只会获得所有函数调用的一个实例。

此处描述了该问题。

于 2012-07-07T19:14:35.903 回答
1

这两个函数的行为不同,因为在第一个中,您将局部变量重新定义var[0] + [4](不附加到用 初始化的原始列表[0]),而在第二个中,您实际上是在附加到该原始初始化列表以将其从[0]to 中获取[0, 4]。正如您所提到的 - 默认值仅评估一次。

于 2012-07-07T19:15:50.260 回答
1

因为这两个代码片段做了两个本质上不同的事情。var + [4]创建一个包含添加列表内容的新列表var4然后我们将这个新列表分配给var. var.append(4)另一方面,将数字 4 附加到现有列表中。

您的问题是var=[0]只评估一次然后重用,因为您append更改了变量本身,您将在以后的调用中看到效果。其他代码仅将新对象分配给局部变量,以后不会产生影响。

通常你不使用可修改的默认值,而是写:

def foo(var=None):
    if var is None: var = [0]

如果您需要记住早期调用的数据,请将整个函数放入一个类中。

于 2012-07-07T19:16:22.447 回答
1

如果你这样写,可能更容易想象正在发生的事情

t1 = [0]
def a(var=t1):
    print (var)
    var = var + [4]

t2 = [0]
def b(var=t2):
    print (var)
    var.append(4)
于 2012-07-07T19:18:05.243 回答
0

这很简单。在第一个函数中a(),您正在将“名称”重新分配var给一个新列表,其中包含旧的var和的内容[4]。在第二个函数中b(),您正在访问实际var对象而不更改名称所指的内容。b()有关为什么以这种方式响应的解释,请参阅此问题: Python 中的“Least Astonishment”:可变默认参数

于 2012-07-07T19:17:50.760 回答