码迷,mamicode.com
首页 > 编程语言 > 详细

python 当list,dic作为默认参数的坑爹之处

时间:2015-08-08 18:22:56      阅读:155      评论:0      收藏:0      [点我收藏+]

标签:

先看代码:

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~








版权声明:本文为博主原创文章,未经博主允许不得转载。

python 当list,dic作为默认参数的坑爹之处

标签:

原文地址:http://blog.csdn.net/emaste_r/article/details/47358843

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!