在编写 Python 程序时,我们经常会给函数参数设置默认值,以便让函数调用更方便。但如果默认值是列表、字典这样的“可变对象”,就很容易引发程序行为异常的问题。

这篇文档将通过简单清晰的语言和示例,帮助大家理解这个问题的本质,并学会如何正确处理它。


一、为什么会出问题?

在 Python 中,函数参数的默认值只会在函数“定义”的时候执行一次。这个值会被保存在内存里,在后续每次调用函数时都会复用它。

这没问题,除非你用了“可变对象”做默认值,比如列表:

示例

class Example:
    def __init__(self, items=[]):  # 使用了可变对象作为默认值
        self.items = items

    def add_item(self, item):
        self.items.append(item)

obj1 = Example()
obj2 = Example()
obj1.add_item("a")
obj2.add_item("b")

print(obj1.items)  # ['a', 'b']
print(obj2.items)  # ['a', 'b'] → 本来希望只看到 ['b']

本意是每个对象有独立的列表,但两个对象居然共用了同一个列表!

这就是因为 items=[] 中的列表是在函数“定义”时就创建好的,obj1 和 obj2 实际引用了同一个列表。


二、这个问题会导致什么?

  1. 不同函数调用之间互相影响
    • 如果你多次调用某个函数,它们可能会共享一个默认列表。
  2. 不同对象之间出现状态污染
    • 类的多个实例原本应该互不干扰,但却共享了一个成员变量,结果互相“串门”。
  3. 调试非常困难
    • 因为问题不是出现在“调用”时,而是“定义”时,很难第一时间想到是参数默认值的问题。

三、根本原因解析

我们再强调一下核心原因:

  • Python 中,函数默认参数值只会被求值一次。
  • 如果这个值是可变的(如列表),那后续的函数调用都可能使用并修改这个共享对象。

比如:

def foo(mylist=[]):
    mylist.append(1)
    return mylist

print(foo())  # [1]
print(foo())  # [1, 1] ← 第二次调用在第一次的基础上增加了!

可变对象的这个特性,会导致数据在不经意间被“保留下来”,从而引发意料之外的程序行为。


四、如何解决这个问题?

推荐方法是:

默认参数用 None,在函数内部判断并创建新的对象。

改写上面的类和函数如下:

class Example:
    def __init__(self, items=None):
        if items is None:
            items = []
        self.items = items

函数的例子也类似:

def foo(mylist=None):
    if mylist is None:
        mylist = []
    mylist.append(1)
    return mylist

这样,每次调用都会创建一个独立的新列表,不会发生数据共享。


五、在哪些地方要特别注意这个问题?

这个问题在以下几种场景特别常见:

  • 类的 __init__ 方法中,用默认参数初始化成员变量时
  • 工具函数或递归函数中,带有累积逻辑的参数
  • 函数嵌套或装饰器传参场景

只要用到了“可变对象作为默认值”,就要保持警惕。


六、总结

使用可变默认参数是 Python 编程中的一个“老坑”。很多初学者甚至中级开发者都可能会不小心踩到它。

通过记住一个简单的原则:默认参数用 None,在函数内部初始化,就能从根本上避免这个问题。

理解这个机制之后,不仅能让你写出更健壮的 Python 代码,还能加深你对 Python 参数传递与内存管理机制的认识。

Updated: