先看代码:
def foo(a, b =[]): b.append(a) print b foo(1) foo(2)
结果想象中是:
>>>
[1]
[2]
>>>
实际上是:
>>>
[1]
[1, 2]
>>>
查看官方文档:https://docs.python.org/2.7/tutorial/controlflow.html#default-argument-values
The default values are evaluated at the point of function definition in the defining scope.
The default value is evaluated only once.
This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes.
大致翻译下:
默认参数仅仅在def语句被执行的时候被赋值,而且就赋值一次。遇到可变类型的list或者dic,就可能出现一些想不到的不同呢。
在知乎某问题上:http://www.zhihu.com/question/21924859 提到了当python执行def的时候,会根据编译好的字节码和命名空间新建一个函数,并且会计算默认参数的值。默认参数作为这个新建的函数的一个属性(没错,函数也是一个对象),看下面:
>>> def foo(bar=[]): ... return bar >>> foo.func_name 'foo' >>> foo.func_defaults ([],)
在陷阱!python参数默认值 文章提到一段代码:
def a(): print 'a executed' return [] def b(x=a()): print 'id(x):',id(x) x.append(5) print 'x:',x for i in range(2): print '*'*20 b() print 'b.__defaults__:',b.__defaults__ print 'id(b.__defaults__[0]):',id(b.__defaults__[0]) for i in range(2): print '*'*20 b(list()) print 'b.__defaults__:',b.__defaults__ print 'id(b.__defaults__[0]):',id(b.__defaults__[0])
结果:
>>> a executed ******************** id(x): 48161096 x: [5] b.__defaults__: ([5],) id(b.__defaults__[0]): 48161096 ******************** id(x): 48161096 x: [5, 5] b.__defaults__: ([5, 5],) id(b.__defaults__[0]): 48161096 ******************** id(x): 48033160 x: [5] b.__defaults__: ([5, 5],) id(b.__defaults__[0]): 48161096 ******************** id(x): 48032776 x: [5] b.__defaults__: ([5, 5],) id(b.__defaults__[0]): 48161096 >>>
分析下:
从“a executed”看出:在执行def之前,就已经把参数默认值给计算出来了。
一共四次调用b(),前两次没有传参,用的是默认参数。下面结果可以看出x的确是使用默认参数:
id(x): 48161096
id(b.__defaults__[0]): 48161096
第三次b(),传参list(),所以x是自己的id,不需要用默认参数了:
id(x): 48033160
id(b.__defaults__[0]): 48161096
第四次b(),传参list(),x又是一个新的id,不需要用默认参数了:
id(x): 48032776
id(b.__defaults__[0]): 48161096
总结下:
python的默认参数就在b.__defaults__这个列表中,遇到需要用到默认参数的就会调用它,它就安安静静呆在那里,一个人。(突然画风变了呢?)
回到题目最初的代码:
def foo(a, b =[]): b.append(a) print b foo(1) foo(2)
因为没有传入参数,所以就用默认参数,默认的b是个list啊,所以才越append越多。
官方文档中的修改措施:
def foo(a, b=None): if b is None: b = [] b.append(a) print b foo(1) foo(2)
=========================增加于20150808===========================
看到一段代码,在class中的def中,默认参数的id也是同一个吗?
如果我有很多个class的实例obj,每个obj的id应该是不一样的吧?obj中的函数的默认参数的id应该是不一样的吧。
class demo_list: def __init__(self, l=[]): self.l = l print 'id(self.l):',id(self.l) def add(self, ele): self.l.append(ele) def appender(ele): obj = demo_list() print 'id(obj):',id(obj) obj.add(ele) print obj.l if __name__ == "__main__": for i in range(5): appender(i)
结果是:
id(self.l): 49415560 id(obj): 49357000 [0] id(self.l): 49415560 id(obj): 49277192 [0, 1] id(self.l): 49415560 id(obj): 49277192 [0, 1, 2] id(self.l): 49415560 id(obj): 49277192 [0, 1, 2, 3] id(self.l): 49415560 id(obj): 49277192 [0, 1, 2, 3, 4]
除了第一个obj,每个obj都是指向同一个id。
每个obj的默认参数都是指向同一个id,好吧,默认参数l是同一个对象。
我们先把关注点放到默认参数上,如果我指定参数呢?
把obj = demo_list() 改成:obj = demo_list(list())
结果是:
id(self.l): 48242888 id(obj): 48243400 [0] id(self.l): 48163080 id(obj): 48243400 [1] id(self.l): 48243400 id(obj): 48246024 [2] id(self.l): 48246024 id(obj): 48244296 [3] id(self.l): 48244296 id(obj): 48249800 [4]
可以看到每个l都是不同的对象,并没有用到默认参数了呢,恩,这才是对的。
然后id(obj)也大致不一样。【此处有坑,待验证】
关于obj的id问题,我猜是python的优化问题,对于小的东西比如int(-5~256)都是预分配了的,所以i=10和j=10都会优化成指向同一个id的对象。那么我大胆猜测,当一个对象比较小的时候,对于相似的东西,python也是指向同一个id的对象。上述第一段代码都指向同一个list,相似度高,同样的obj的id就多,第二段代码因为每个list的id都不一样了,相似度低,obj有些id就一样,有些就不一样了呢。
根据上述我的猜测,我就做了一个实验,把class demo_list变得稍微复杂一点,稍微大一点的东西,python就不容易做优化。
class demo_list: def __init__(self, l=[]): self.l = l print 'id(self.l):',id(self.l) def add(self, ele): self.l.append(ele) def a(self): pass def b(self): pass def c(self): pass def appender(ele): obj = demo_list(list()) print 'id(obj):',id(obj) obj.add(ele) print obj.l if __name__ == "__main__": for i in range(5): appender(i)
结果:
id(self.l): 48899656 id(obj): 48898248 [0] id(self.l): 48818440 id(obj): 48819592 [1] id(self.l): 48819592 id(obj): 48901256 [2] id(self.l): 48901256 id(obj): 48901384 [3] id(self.l): 48901384 id(obj): 48898248 [4]
结果看起来,果然多了三个函数a(),b(),c()后,每个obj的id都不一样呢。
哎,这优化,也不知道该怎么说它好呢。其实吧,理论上,每一个obj本来就应该不一样id的说。
咦,那么这个参数默认值不就可以当成一个静态变量(虽然py没听过支持静态变量的说)来使用咯?
参数默认值只被执行一次 == 静态变量只被初始化一次。
貌似还真可以。
wow~ so cool~
版权声明:本文为博主原创文章,未经博主允许不得转载。