Python 对函数默认参数的处理

Python 对函数默认参数的处理

一个以可变对象为默认参数的函数:

1
2
3
4
5
6
7
8
9
10
11
class A:
def __init__(self):
self.x = 1
print('created')

def f(a = A()):
a.x += 1
print(a.x)
# output: created
f() # output: 2
f() # output: 3

从结果可以看出,Python 在解析时便已经创建好了默认参数 a 的值。函数 f 在调用时采用了同一个对象,而不是每次调用时重新创建新的对象。这一点与 C++ 的处理方式不同。

f.__defaults__

在 Python 中,函数属于一等公民(first-class)。函数可当作一个对象,拥有自己的属性与方法。而默认参数则存在与函数的一个属性中。

1
2
3
4
5
6
In [28]: def f(a, b=1):
2 pass


In [29]: f.__defaults__
Out[29]: (1, [])

Python 在解析代码时,便会将默认参数存于 f.__defaults__ 中。

在 CPython 的 (funcobject.h)[https://github.com/python/cpython/blob/master/Include/funcobject.h] 中也可以看到,PyFunctionObject 的一个属性便是 func_defaults, 对应 Python 中每个函数中的 __defaults__ 属性

默认参数 与 闭包

以下是一个经常被提起的关于理解闭包的一段代码

1
2
3
4
5
6
7
8
9
10
def test():
lst = []
for i in range(5):
lst.append(lambda x: x * i)
return lst


lst = test()
[f(1) for f in lst]
# Output: [4, 4, 4, 4, 4]

在这里中,匿名函数中的变量 i 并没有在声明时便被求值,而是在匿名函数被调用时才被求值。因为 i 来自于 test 函数,且在匿名函数被调用时,循环已经结束,所以 i 的值已经变成 4。

但如果想让 i 在匿名函数声明时便被求值该如何做呢?
以下代码便借助默认参数解决问题:

1
2
3
4
5
6
7
8
9
10
def test():
lst = []
for i in range(5):
lst.append(lambda x, i=i: x * i)
return lst

lst = test()

[f(1) for f in lst]
# Output: [0, 1, 2, 3, 4]

与第一段代码相比,第二段代码中的 i 在匿名函数声明时就被求值,而且因此,每个匿名函数拥有的默认参数 i 的值都不相同。这样解决了问题。

参考

  1. funcobject.h
  2. Where is the default parameter in Python function
  3. Default Parameter Values in Python