rank vote view answer url
26 2114 118677 30 url

Python中的"小震撼":变化的默认参数

许多 Python 老手也被下面的问题困扰:

def foo(a=[]):
    a.append(5)
    return a

Python 新手估计可能会想这个函数返回一个只有元素[5]的列表.但是结果却出人意料:

>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()

我的一个经理曾经碰到过这个特性并把它叫做语言的"动态设计缺陷". 这个现象应当有更深层次的解释, 如果你不懂它的内部它确实非常令人困惑. 然而我不能回答下面的问题:是什么原因使默认参数在函数的定义时被绑定,而不是在执行时?我怀疑这个特性在现实中有没有实际的用途(就像在C语言中有谁去用静态变量?)


事实上这并不是设计缺陷,也不是什么内部或性能原因.

原因很简单,Python中的函数是最高等级的对象,而不仅仅是一小段代码.

试着这么来理解:一个函数是一个被它自己定义而执行的对象;默认参数是一种"成员数据",所以它们的状态和其他对象一样,会随着每一次调用而改变.

Effbot在它的Python中的默认参数对这种行为的原因解释的非常清楚!我强烈建议你读一读能对函数对象的工作原理有更深一步的了解.

Python 中的默认参数(文章翻译)

Python 默认参数很容易让新手犯错.

引起错误的原因通常是使用可变(mutable)对象作为了参数的默认值, 这个可变对象是可以被修改的, 比如说列表(list)和字典(dict)

例如:

>>> def function(data=[]):
...     data.append(1)
...     return data
...
>>> function()
[1]
>>> function()
[1, 1]
>>> function()
[1, 1, 1]

可以看到上面的列表(list)越来越长, 如果检查这个列表的id, 会发现他返回的是同一个值:

>>> id(function())
12516768
>>> id(function())
12516768
>>> id(function())
12516768

原因很简单: 在每次函数调用的时候都使用同一个对象(object), 这个可修改的特性我们叫做 "sticky".

原理

只有当"def"语句执行的时候才会对默认参数值进行赋值, 可以看看文档的定义:

https://docs.python.org/2.0/ref/function.html

还要注意的是在Python 中 "def" 是可执行语句, 默认参数仅在"def" 语句环境里生效.每次执行"def"语句都会创建一个新的函数对象(每次都会对默认参数进行赋值)