Python函数的默认参数设计示例详解
在Python教程里,针对默认参数,给了一个“重要警告”的例子:
deff(a,L=[]): L.append(a) returnL print(f(1)) print(f(2)) print(f(3))
默认值只会执行一次,也没说原因。会打印出结果:
[1]
[1,2]
[1,2,3]
因为学的第一门语言是Ruby,所以感觉有些奇怪。但肯定的是方法f一定储存了变量L。
准备知识:指针
p指向不可变对象,比如数字。则相当于p指针指向了不同的内存地址。
p指向的是可变对象,比如list。list自身的改变,并不会改变list对象自身所在的内存地址。所以p指向的内存地址不变。
>>>p=1 >>>id(p) >>>p=p+1 >>>id(p) >>>p=11 >>>id(p) >>>p=[] >>>id(p) >>>p.append(11) >>>id(p)
根本原因
Python函数的参数默认值,是在编译阶段就绑定了。(写代码时就定义了。)
下面是一段从PythonCommonGotchas中摘录的原因解释:
Python'sdefaultargumentsareevaluatedoncewhenthefunctionisdefined,noteachtimethefunctioniscalled(likeitisinsay,Ruby).Thismeansthatifyouuseamutabledefaultargumentandmutateit,youwillandhavemutatedthatobjectforallfuturecallstothefunctionaswell.
由此可知:
- 在运行代码时,运行到函数定义时,默认参数的表达式就被执行了。
- 函数调用时,不会再次运行默认参数的表达式。⚠️这点和Ruby完全不同。
- 由此可知,如果默认参数,指向一个不变对象,例如L=1。那么在函数调用时,在函数体内对L重新赋值,L其实是一个新的指针,指向的是一个新的内存地址。而原来默认参数L本身及指向的内存地址,已经储存在最开始编译时的函数定义中。可以用__default__查看。
- 如果默认参数指向的是一个可变对象,如list,那么L.append(a)是对可变对象自身的修改,L指向的内存地址不变。所以每次调用函数,默认参数取出的都是这个内存地址的对象。
第三条,修改上面的例子:
deff(a,L=1): L=a print(id(L)) returnL print("self",id(f.__defaults__[0])) print(f(1)) print("self",id(f.__defaults__[0])) print(f(33)) print("self",id(f.__defaults__[0])) #运行结果: self4353170064 1 self4353170064 33 self4353170064
默认参数L,在编译阶段就绑定了,储存在__default__内。函数体内的L=a表达式,生成的是新的变量。返回的L是新的变量,和默认参数无关。
第四条,还是上面的例子,改一下默认参数的类型为可变对象list:
deff(a,L=[]): L.append(a) print(id(L)) returnL #L=f(1) print("self",id(f.__defaults__[0])) print(f(1)) print("self",id(f.__defaults__[0])) print(f(33)) print("self",id(f.__defaults__[0]))
#返回结果 self4337586048 [1] self4337586048 [1,33] self4337586048
由id号可知,返回的是默认参数自身。
如何避免这个陷阱带来不必要麻烦
deff(a,L=None): ifLisNone: L=[] L.append(a) returnL
为什么Python要这么设计
StackOverflow上争论很多。
Ruby之所以没有这个问题,我想是因为Ruby的def关键字定义了一个封闭作用域,任何数据都必须通过参数传入到方法内,才能用。
和Ruby比,Python参数的作用被大大消弱了。Python的出现晚于Ruby,其创始人肯定参考了Ruby的设计。抛弃了这个设计,选择了类似javascript的函数方式。def定义的函数,执行时是可以接收外部作用域的变量的。
有观点认为:
出于Python编译器的实现方式考虑,函数是一个内部一级对象。而参数默认值是这个对象的属性。在其他任何语言中,对象属性都是在对象创建时做绑定的。因此,函数参数默认值在编译时绑定也就不足为奇了。
本文参考了:http://cenalulu.github.io/python/default-mutable-arguments/#toc1,并加入了自己的理解。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。