题目
任何长时间学习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中使用静态变量而没有引发bug)。
这里有一个有趣的例子:
>>> def a():
... print("a executed")
... return []
...
>>>
>>> def b(x=a()):
... x.append(5)
... print(x)
...
a executed
>>> b()
[5]
>>> b()
[5, 5]
默认参数在函数定义时就已经计算好了。
回答一
事实上,这并不是一个设计缺陷,而且与内部实现、性能无关。
这仅仅是因为一个事实:在Python中函数是一等公民,而不只是代码片段。
继续深入思考,你会觉得这是合理的:函数也是对象,在定义时被执行得到的对象;默认参数类似“成员数据”,因此它们的状态在多次调用后会发生改变——就如在任何其他对象里一样。
我觉得这篇文章简洁明了,如果想对函数对象是如何工作的有更好的理解,也建议阅读。
为什么会这样
默认参数值会被计算,当且仅当其所属的def语句被执行。
def是Python中的可执行语句,而且默认参数是在def语句环境里被计算。如果执行def语句多次,每次它将会产生新的函数对象(默认参数也会重新计算)
替代方法
使用占位符代替修改默认值,None是个很好的选择。
def myfunc(value=None):
if value is None:
value = []
# modify value here
具体是怎么执行的
当Python执行def语句时,它需要一些已经生成的部分(包括函数体和当前命名空间的编译代码)创建一个新的函数对象。默认参数也是在这时候被计算的。
各个部分作为函数对象的属性:
>>> function.func_name
'function'
>>> function.func_code
00BEC770, file "", line 1>
>>> function.func_defaults
([1, 1, 1],)
>>> function.func_globals
{'function': 0x00BF1C30>,
'__builtins__': '__builtin__' (built-in)>,
'__name__': '__main__', '__doc__': None}
因为我们可以访问到默认参数,因此可以改变它们
>>> function.func_defaults[0][:] = []
>>> function()
[1]
>>> function.func_defaults
([1],)
另一种重置默认参数的方法时简单地重新执行相同的def语句。Python将会创建创建新的绑定给这个函数对象,重新计算默认参数,像之前一样将函数对象赋值给同一个变量。但是话说回来,当且仅当你知道你在做什么时才这么去使用。