首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

陷阱!python参数默认值

在stackoverflow上看到这样一个程序:

classdemo_list:

def__init__(self, l=[]):

self.l = l

defadd(self, ele):

self.l.append(ele)

defappender(ele):

obj = demo_list()

obj.add(ele)

printobj.l

if__name__ =="__main__":

foriinrange(5):

appender(i)

输出结果是

[]

[,1]

[,1,2]

[,1,2,3]

[,1,2,3,4]

有点奇怪,难道输出不应该是像下面这样吗?

[]

[1]

[2]

[3]

[4]

其实想要得到上面的输出,只需要将obj = intlist()替换为obj = intlist(l=[])。

默认参数工作机制

上面怪异的输出简单来说是因为:

Default values are computed once, then re-used.

def__init__(self, l=[]):

printid(l),

self.l = l

输出结果为:

可以清晰看出每次调用函数时,默认参数l都是同一个对象,其id为4346933688。

关于默认参数,文档中是这样说的:

Default parameter values are evaluated when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that the same “pre-computed” value is used for each call.

为了能够更好地理解文档内容,再来看一个例子:

defa():

print"a executed"

return[]

defb(x=a()):

print"id(x): ", id(x)

x.append(5)

print"x: ", x

foriinrange(2):

print"-"*15,"Call b()","-"*15

b()

printb.__defaults__

print"id(b.__defaults__[0]): ", id(b.__defaults__[])

foriinrange(2):

print"-"*15,"Call b(list())","-"*15

b(list())

printb.__defaults__

print"id(b.__defaults__[0]): ", id(b.__defaults__[])

注意,当python执行def语句时,它会根据编译好的函数体字节码和命名空间等信息新建一个函数对象,并且会计算默认参数的值。函数的所有构成要素均可通过它的属性来访问,比如可以用funcname属性来查看函数的名称。所有默认参数值则存储在函数对象的_defaults属性中,它的值为一个列表,列表中每一个元素均为一个默认参数的值。

好了,你应该已经知道上面程序的输出内容了吧,一个可能的输出如下(id值可能为不同):

我们看到,在定义函数b(也就是执行def语句)时,已经计算出默认参数x的值,也就是执行了a函数,因此才会打印出a executed。之后,对b进行了4次调用,下面简单分析一下:

如果上面的内容你已经搞明白了,那么你可能会觉得默认参数值的这种设计是python的设计缺陷,毕竟这也太不符合我们对默认参数的认知了。然而事实可能并非如此,更可能是因为:

Functions in Python are first-class objects, and not only a piece of code.

我们可以这样解读:函数也是对象,因此定义的时候就被执行,默认参数是函数的属性,它的值可能会随着函数被调用而改变。其他对象不都是如此吗?

可变对象作为参数默认值?

参数的默认值为可变对象时,多次调用将返回同一个可变对象,更改对象值可能会造成意外结果。参数的默认值为不可变对象时,虽然多次调用返回同一个对象,但更改对象值并不会造成意外结果。

因此,在代码中我们应该避免将参数的默认值设为可变对象,上面例子中的初始化函数可以更改如下:

def__init__(self, l=None):

ifnotl:

self.l = []

else:

self.l = l

在这里将None用作占位符来控制参数l的默认值。不过,有时候参数值可能是任意对象(包括None),这时候就不能将None作为占位符。你可以定义一个object对象作为占位符,如下面例子:

sentinel = object()

deffunc(var=sentinel):

ifvarissentinel:

pass

else:

printvar

虽然应该避免默认参数值为可变对象,不过有时候使用可变对象作为默认值会收到不错的效果。比如我们可以用可变对象作为参数默认值来统计函数调用次数,下面例子中使用collections.Counter()作为参数的默认值来统计斐波那契数列中每一个值计算的次数。

deffib_direct(n, count=collections.Counter()):

assertn >,'invalid n'

count[n] +=1

ifn

returnn

else:

returnfib_direct(n -1) + fib_direct(n -2)

printfib_direct(10)

printfib_direct.__defaults__[]

运行结果如下:

89

Counter()

我们还可以用默认参数来做简单的缓存,仍然以斐波那契数列作为例子,如下:

deffib_direct(n, count=collections.Counter(), cache={}):

assertn >,'invalid n'

count[n] +=1

ifnincache:

returncache[n]

ifn

value = n

else:

value = fib_direct(n -1) + fib_direct(n -2)

cache[n] = value

returnvalue

printfib_direct(10)

printfib_direct.__defaults__[]

结果为:

89

Counter({2:2,3:2,4:2,5:2,6:2,7:2,8:2,1:1,9:1,10:1})

这样就快了太多了,fib_direct(n)调用次数为o(n),这里也可以用装饰器来实现计数和缓存功能。

作者:selfboot © 著作权归作者所有

链接:http://selfboot.cn/2014/10/27/python_default_values/

Python网络爬虫与数据挖掘

学习Python和网络爬虫关注公众号:datanami

入群请回复「学习」

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180111B0K5HX00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券