Python中的默认参数

by LauCyun Jan 14,2016 17:27:02 3,842 views

Ps:前面《Python中的“小震撼”:变化的默认参数》文章中引用了本文,所以特此翻译分享给大家。

对Python默认参数值的处理方法是少有的几个容易使大多数新手Python程序员犯错的地方之一(通常只犯一次)。

导致困惑的地方是当你使用“可变”对象作为(参数的)默认值时的(程序)行为。(可变)也就是说值可以原地修改,例如:列表或字典。

先举个栗子:

>>> def function(data=[]):
...     data.append(1)
...     return data
...
>>> function()
[1]
>>> function()
[1, 1]
>>> function()
[1, 1, 1]
>>>

正如你看到的那样,列表变得越来越长。如果你查看列表的标识符,你会发现函数实际上总是返回同一个对象。

>>> id(function())
48163848L
>>> id(function())
48163848L
>>> id(function())
48163848L
>>>

原因很简单:函数在每次调用时总是使用同一个对象。我们做的修改行为是“粘性”。

 

为什么会这样?

属于函数定义的默认参数值,当且仅当def语句执行时求值。请看:

http://docs.python.org/ref/function.html (链接过期)

语言参考中的相关章节。

要注意Python中的def语句是可执行的,而且默认参数是在def语句的环境下求值的。如果def语句执行了多次,那么它每次将产生新的函数对象(对象会带着全新的默认值)。下面我们会看到这样的栗子的。

 

那要怎么做?

迂回方法是,就像其他人已经提到了的,使用占位符值来替代默认值。None是一个常用的值:

def myfunc(value=None):
    if value is None:
        value = []
    # modify value here

如果你需要处理任意对象(包括None),你可以使用哨兵对象:

sentinel = object()

def myfunc(value=sentinel):
    if value is sentinel:
        value = expression
    # use/modify value here

在很早以前,即在“object”对象引入之前,有时你会看到像下面这样的代码:

sentinel = ['placeholder']

这个代码用于创建一个具有唯一ID的对象;这对中括号([])在每次执行时产生新的列表。

 

对可变默认参数的合法(合理)使用

最后,要提到很多高级的Python代码经常使用这个机制的好处。例如,假设你要在一个循环里创建了一堆UI按钮,而你可能会使用像下面这样的代码:

for i in range(10):
    def callback():
        print "clicked button", i
    UI.Button("button %s" % i, callback)

这样你会发现所有的回调函数都打印出相同的值(在这个情况下,很可能是9)。原因是Python的嵌套作用域是绑定到变量的,而不是绑定到对象值的。所以所有的回调函数实例将会看到当前(也是最后)的变量“i”的值。为了修正这个问题,使用下面的代码:

for i in range(10):
    def callback(i=i):
        print "clicked button", i
    UI.Button("button %s" % i, callback)

那个“i=i”的步伐将绑定参数“i”(一个局部变量)到当前的外部变量“i”的值。

两个其他的例子使用的是局部缓存:

def calculate(a, b, c, memo={}):
    try:
        value = memo[a, b, c] # return already calculated value
    except KeyError:
        value = heavy_calculation(a, b, c)
        memo[a, b, c] = value # update the memo dictionary
    return value

(这对某些递归算法很好)

另一个例子,对应高度优化的代码,对全局名字的局部重新绑定:

import math

def this_one_must_be_fast(x, sin=math.sin, cos=math.cos):
    ...

 

这是怎么工作的?

当Python执行def语句时,它使用一些已有的东西(包括编译了的函数体的代码和当前的名字空间),然后创建出一个新的函数对象。当它做这个的时候,默认值也会被求值。

这些各种各样的组件也能作为函数对象的属性而访问。使用我们先前定义过的函数:

>>> function.func_name
'function'
>>> function.func_code
<code object function at 00BEC770, file "<stdin>", line 1>
>>> function.func_defaults
([1, 1, 1],)
>>> function.func_globals
{'function': <function function at 0x00BF1C30>,
'__builtins__': <module '__builtin__' (built-in)>,
'__name__': '__main__', '__doc__': None}

由于你可以访问它们,因而你也可以修改它们:

>>> function.func_defaults[0][:] = []
>>> function()
[1]
>>> function.func_defaults
([1],)

当然,这可不是我推荐的正常使用方法。

另一个重置默认值的方法就是简单的重新执行一下那个相同的def语句。Python会重新创建一个新的到这个代码对象绑定,重新计算默认值,然后将这个函数对象赋值给同以前一样的那个变量。但是要着重指出,请在你确切知道你在做什么的时候才能这么做。

最后,如果你刚好有函数各个部分,而不是函数本身,你可以使用new模块中的function类来创建你自己的函数对象。

 

本文翻译于Default Parameter Values in Python

(全文完)

Tags