1

我在 Expert Python Programming 中读到过这个极端案例。检查此代码:

def f(arg={}):
    arg['3'] = 4
    return arg

>>> print f()
{'3': 4}
>>> res = f()
>>> res['4'] = 'Still here'
>>> print f()
{'3': 4, '4': 'Still here'}

我不清楚为什么f最后一次调用时(在保存其返回值之后),而不是分配 arg 空字典(因为它是在没有参数的情况下调用的),而是保留旧的引用。

这本书是这样说的:“如果在参数中创建了一个对象,那么如果函数返回该对象,则参数引用仍然有效”。

我明白“这就是它的工作方式”,但为什么会这样呢?

4

3 回答 3

2

您的问题默认为可变参数(在本例中为字典):

def f(arg={}):
    arg['3'] = 4
    return arg

应该:

def f(arg=None):
    arg = arg if arg is not None else {}
    arg['3'] = 4
    return arg

产量:

>>> print f()
{'3': 4}
>>> res = f()
>>> res['4'] = 'Still here'
>>> print f()
{'3': 4}

就像你期望的那样。

这里的问题是默认参数是在首次定义/解析函数时评估的,而不是在调用它们时。这只是您需要注意的 python 解析器的细微差别。

至于为什么,请查看“Least Astonishment”和 Mutable Default Argument

于 2012-02-09T17:06:52.983 回答
2

因为默认参数只评估一次,所以在评估和创建函数时(例如,它们是函数定义的一部分,可以通过 inspect.getargspec 获取)。

由于它们是函数的一部分,因此对函数的每次调用都将具有相同的默认值实例。如果它是一个不可变的值,这不是问题,但是一旦它是可变的,它就会成为一个陷阱。

给定类定义,类定义中存在相同的“特征”:

class A(object):
    foo = {}

打电话

x = A() 
y = A()
x.foo['bar'] = "baz"

...会给出 y.foo['bar'] 的计算结果为“baz”,因为 x 和 y 具有相同的foo。这就是为什么成员初始化应该在init而不是类体中完成。

于 2012-02-09T17:17:14.043 回答
1

默认参数在声明函数时创建一次,因此每次调用 f() 都会获得相同的字典实例,该实例开始时为空。这能回答问题吗?

于 2012-02-09T17:14:41.897 回答