首页 > 解决方案 > 函数结果与可变默认参数的连接

问题描述

假设一个具有可变默认参数的函数:

def f(l=[]):
    l.append(len(l))
    return l

如果我运行这个:

def f(l=[]):
    l.append(len(l))
    return l
print(f()+["-"]+f()+["-"]+f()) # -> [0, '-', 0, 1, '-', 0, 1, 2]

或这个:

def f(l=[]):
    l.append(len(l))
    return l
print(f()+f()+f()) # -> [0, 1, 0, 1, 0, 1, 2]

而不是以下更合乎逻辑的:

print(f()+f()+f()) # -> [0, 0, 1, 0, 1, 2]

为什么?

标签: pythonpython-3.x

解决方案


这实际上很有趣!

我们知道,函数定义中的列表l只在该函数的定义处被初始化一次,并且对于该函数的所有调用,该列表将恰好有一份副本。现在,该函数修改了这个列表,这意味着对该函数的多次调用将多次修改完全相同的对象。这是第一个重要的部分。

现在,考虑添加这些列表的表达式:

f()+f()+f()

根据运算符优先法则,这等价于以下内容:

(f() + f()) + f()

...与此完全相同:

temp1 = f() + f() # (1)
temp2 = temp1 + f() # (2)

这是第二个重要的部分。

添加列表会生成一个对象,而无需修改其任何参数。这是第三个重要部分。

现在让我们将我们所知道的结合在一起。

在上面的第 1 行中,第一个调用返回[0],正如您所期望的那样。[0, 1]如您所料,第二个调用返回。等一下!修改后,该函数将一遍又一遍地返回完全相同的对象(不是它的副本!)!这意味着第一个调用返回的对象现在也变成[0, 1]!这就是为什么temp1 == [0, 1] + [0, 1]

然而,加法的结果是一个全新的对象,因此[0, 1, 0, 1] + f()与 相同[0, 1, 0, 1] + [0, 1, 2]。请注意,第二个列表也是您希望函数返回的内容。当您添加时会发生同样的事情f() + ["-"]:这会创建一个 list对象,因此任何其他调用f都不会干扰它。

您可以通过连接两个函数调用的结果来重现这一点:

>>> f() + f()
[0, 1, 0, 1]
>>> f() + f()
[0, 1, 2, 3, 0, 1, 2, 3]
>>> f() + f()
[0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5]

同样,您可以这样做,因为您正在连接对同一 object 的引用。


推荐阅读