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

先看代码:

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~

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

时间: 2024-08-04 21:12:17

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

在python函数中默认参数的一些坑

一.默认参数 python为了简化函数的调用,提供了默认参数机制: 这样在调用pow函数时,就可以省略最后一个参数不写: 在定义有默认参数的函数时,需要注意以下: 必选参数必须在前面,默认参数在后: 设置何种参数为默认参数?一般来说,将参数值变化小的设置为默认参数. python标准库实践 python内建函数: 函数签名可以看出,使用print('hello python')这样的简单调用的打印语句,实际上传入了许多默认值,默认参数使得函数的调用变得非常简单. 二.出错了的默认参数 引用一个官

Python函数的默认参数的设计【原创】

在Python教程里,针对默认参数,给了一个“重要警告”的例子: def f(a, L=[]): L.append(a) return L print(f(1)) print(f(2)) print(f(3)) 默认值只会执行一次,也没说原因.会打印出结果: [1] [1, 2] [1, 2, 3] 因为学的第一门语言是Ruby,所以感觉有些奇怪. 但肯定的是方法f一定储存了变量L. 准备知识:指针 p指向不可变对象,比如数字.则相当于p指针指向了不同的内存地址. p指向的是可变对象,比如lis

python函数中的默认参数问题

Python 函数中,参数的传递本质上是一种赋值操作 def foo(arg): arg = 2 print(arg) a = 1 foo(a) # 输出:2 print(a) # 输出:1 def bar(args): args.append(1) b = [] print(b)# 输出:[] print(id(b)) # 输出:4324106952 bar(b) print(b) # 输出:[1] print(id(b)) # 输出:4324106952 def foo(x, a_list=

Python 必选参数,默认参数,可变参数,关键字参数和命名关键字参数

Py的参数还真是多,用起来还是很方便的,这么多参数种类可见它在工程上的实用性还是非常广泛的. 挺有意思的,本文主要参照Liaoxuefeng的Python教程. #必选参数 def quadratic(a, b, c): if not isinstance(a, (int, float)) or not isinstance(b, (int, float)) or not isinstance(c, (int, float)): raise TypeError('bad operand type

C/C++ Python的函数默认参数

发现C/C++  Python的函数可以使用默认参数,来减少传参时候的参数个数. 但是:这样的默认参数最好是不变对象! #include <stdio.h> #include <string.h> void func_1(int id, char s[], char city[] = "Bejing") { printf("%d, %s, %s",id, s, city); } int main() { func_1(1, "李金旭

Python 默认参数

定义默认参数 定义函数的时候,还可以有默认参数. 例如Python自带的 int() 函数,其实就有两个参数,我们既可以传一个参数,又可以传两个参数: >>> int('123') 123 >>> int('123', 8) 83 int()函数的第二个参数是转换进制,如果不传,默认是十进制 (base=10),如果传了,就用传入的参数. 可见,函数的默认参数的作用是简化调用,你只需要把必须的参数传进去.但是在需要的时候,又可以传入额外的参数来覆盖默认参数值. 我们来定

[Python] partial改变方法默认参数

Python 标准库中 functools库中有很多对方法很有有操作的封装,partial Objects就是其中之一,他是对方法参数默认值的修改. 下面就看下简单的应用测试. #!/usr/bin/env python # -*- coding: utf-8 -*- #python2.7x #partial.py #authror: orangleliu ''' functools 中Partial可以用来改变一个方法默认参数 1 改变原有默认值参数的默认值 2 给原来没有默认值的参数增加默认

我爱Python之位置参数、关键字参数、默认参数

1.位置参数: >>> def check_web_server(host, port, path): print .... >>> check_web_server('www.python.org', 80, '/') 三个参数的顺序必须一一对应,且少一参数都不可以 2.(函数调用里的)关键字参数: 可以让函数更加清晰.容易使用,同时也清除了参数的顺序需求,关键字参数通过“键-值”形式加以指定,用于函数调用 >>> check_web_server(

[python 函数学习篇]默认参数

python函数: 默认参数: retries=4 这种形式 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): while True: ok = raw_input(prompt) if ok in ('y', 'ye', 'yes'): return True if ok in ('n', 'no', 'nop', 'nope'): return False retries = retries - 1 if retri