Python中的默认参数详解
文章的主题
不要使用可变对象作为函数的默认参数例如list,dict,因为def是一个可执行语句,只有def执行的时候才会计算默认默认参数的值,所以使用默认参数会造成函数执行的时候一直在使用同一个对象,引起bug。
基本原理
在Python源码中,我们使用def来定义函数或者方法。在其他语言中,类似的东西往往只是一一个语法声明关键字,但def却是一个可执行的指令。Python代码执行的时候先会使用compile将其编译成PyCodeObject.
PyCodeObject本质上依然是一种静态源代码,只不过以字节码方式存储,因为它面向虚拟机。因此Code关注的是如何执行这些字节码,比如栈空间大小,各种常量变量符号列表,以及字节码与源码行号的对应关系等等。
PyFunctionObject是运行期产生的。它提供一个动态环境,让PyCodeObject与运行环境关联起来。同时为函数调用提供一系列的上下文属性,诸如所在模块、全局名字空间、参数默认值等等。这是def语句执行的时候干的活。
PyFunctionObject让函数面向逻辑,而不仅仅是虚拟机。PyFunctionObject和PyCodeObject组合起来才是一个完整的函数。
下文翻译了一篇文章,有一些很好的例子。但是由于水平有限,有些不会翻译或者有些翻译有误,敬请谅解。如果有任何问题请发邮件到acmerfight圈gmail.com,感激不尽
主要参考资料书籍:《深入Python编程》大牛:shell和Topsky
Python对于函数中默认参数的处理往往会给新手造成困扰(但是通常只有一次)。
当你使用“可变”的对象作为函数中作为默认参数时会往往引起问题。因为在这种情况下参数可以在不创建新对象的情况下进行修改,例如listdict。
>>>deffunction(data=[]): ... data.append(1) ... returndata ... >>>function() [1] >>>function() [1,1] >>>function() [1,1,1]
像你所看到的那样,list变得越来越长。如果你仔细地查看这个list。你会发现list一直是同一个对象。
>>>id(function()) 12516768 >>>id(function()) 12516768 >>>id(function()) 12516768
原因很简单:在每次函数调用的时候,函数一直再使用同一个list对象。这么使用引起的变化,非常“sticky”。
为什么会发生这种情况?
当且仅当默认参数所在的“def”语句执行的时候,默认参数才会进行计算。请看文档描述
https://docs.python.org/2/reference/compound_stmts.html#function-definitions
其中有下面一段
"Defaultparametervaluesareevaluatedwhenthefunctiondefinitionisexecuted.Thismeansthattheexpressionisevaluatedonce,whenthefunctionisdefined,andthatthesame“pre-computed”valueisusedforeachcall.Thisisespeciallyimportanttounderstandwhenadefaultparameterisamutableobject,suchasalistoradictionary:ifthefunctionmodifiestheobject(e.g.byappendinganitemtoalist),thedefaultvalueisineffectmodified.Thisisgenerallynotwhatwasintended.AwayaroundthisistouseNoneasthedefault,andexplicitlytestforitinthebodyofthefunction,e.g.:
defwhats_on_the_telly(penguin=None): ifpenguinisNone: penguin=[] penguin.append("propertyofthezoo") returnpenguin "
"def"是Python中的可执行语句,默认参数在"def"的语句环境里被计算。如果你执行了"def"语句多次,每次它都将会创建一个新的函数对象。接下来我们将看到例子。
用什么来代替?
像其他人所提到的那样,用一个占位符来替代可以修改的默认值。None
defmyfunc(value=None): ifvalueisNone: value=[] #modifyvaluehere
如果你想要处理任意类型的对象,可以使用sentinel
sentinel=object()
defmyfunc(value=sentinel): ifvalueissentinel: value=expression #use/modifyvaluehere