Python 函数默认参数机制

默认参数的计算时机

  • 默认参数在函数定义时计算一次,不是在函数调用时。

    1
    2
    3
    def foo(x=[]):
    x.append(1)
    return x

    每次调用 foo(),都会操作同一个列表对象。

可变对象的陷阱

  • 默认参数如果是 可变对象(如 list, dict, set),就会在多次调用中共享状态

    1
    2
    3
    4
    5
    6
    7
    8
    def add_item(item, bag=[]):
    bag.append(item)
    return bag

    add_item('apple') # ['apple']
    add_item('banana') # ['apple', 'banana']
    add_item('cherry', []) # ['cherry']
    add_item('date') # ['apple', 'banana', 'date']
  • 因此,后续调用会“继承”前一次的内容。

安全写法

使用不可变对象(如 None)作为哨兵值,确保每次都新建对象:

1
2
3
4
5
def add_item(item, bag=None):
if bag is None:
bag = []
bag.append(item)
return bag

内部原理

  • Python 将默认参数保存在函数对象的 __defaults__ 属性中。

    1
    2
    >>> add_item.__defaults__
    ([],)
  • 因此,默认参数本质上是绑定在函数定义上的持久状态

  • Python 的设计哲学之一:函数是对象,默认参数是函数对象的属性。

延伸思考

该机制在某些情况下可以刻意利用

  • 作为缓存(memoization)的一种简单实现。

    1
    2
    3
    4
    def fib(n, _cache={0:0, 1:1}):
    if n not in _cache:
    _cache[n] = fib(n-1) + fib(n-2)
    return _cache[n]

    此时,可变默认参数的“持久性”反而是一个特性