我在专家Python编程中读过这个边缘案例。检查此代码:
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空dict(因为它是在没有参数的情况下调用),它保留了老参考。
这本书如此说:“如果在参数中创建了一个对象,那么如果函数返回对象,参数引用仍将是活动的。”
我理解“这是它的工作方式”,但为什么会这样呢?
答案 0 :(得分: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" and the Mutable Default Argument
答案 1 :(得分:2)
因为默认参数仅被评估一次,所以在评估和创建函数时(它们是函数defenition的一部分,例如可以通过inspect.getargspec获取)。
由于它们是函数的一部分,因此对函数的每次调用都将具有默认值的相同实例。如果它是一个不可变的值,这不是问题,但只要它是可变的,它就会成为一个问题。
在课堂辩护中存在相同的“特征”,给定一个类别辩护:
class A(object):
foo = {}
调用
x = A()
y = A()
x.foo['bar'] = "baz"
...会给y.foo ['bar']评估为“baz”,因为x和y具有相同的 foo。 这就是成员初始化应该在 init 而不是类体中完成的原因。
答案 2 :(得分:1)
当声明函数时,默认参数被创建一次,因此对f()的每次调用都会获得字典的相同实例,该字典从空开始。这回答了这个问题吗?