跳转至

Python类属性中的"共享列表"陷阱:为什么你的朋友成了别人的朋友?

今天给大家分享一个Python中非常容易踩坑的特性——类属性中默认值为可变对象(比如列表)时的诡异行为。

一个看似简单的例子

我们先看一个简单的Person类定义:

1
2
3
4
5
6
7
class Person:
    def __init__(self, name:str, frients:list=[]):
        self.name = name
        self.frients = frients

    def show_frients(self):
        print(f"{self.name}, frients: {self.frients}")

看起来很正常对吧?我们创建一个Person实例时,如果没有提供frients参数,就会默认使用空列表。

诡异的现象出现了

让我们创建两个Person实例:

1
2
3
4
5
6
7
def main():
    zhangsan = Person("zhangsan")
    lisi = Person("lizi")
    zhangsan.frients.append("x")

    zhangsan.show_frients()
    lisi.show_frients()

运行结果会让你大吃一惊:

zhangsan, frients: ['x']
lizi, frients: ['x']

什么?我们只给张三添加了朋友"x",为什么李四也自动拥有了这个朋友?

为什么会这样?

这个现象看似诡异,但其实有合理的解释:

  1. 默认参数在函数定义时就被求值:Python的函数默认参数是在函数定义时(而不是调用时)就创建的
  2. 所有实例共享同一个默认列表:因为没有显式传入frients参数,两个实例都使用了同一个默认列表对象
  3. 修改的是同一个列表:当通过一个实例修改列表时,所有使用默认列表的实例都会受到影响

如何避免这个坑?

方法1:显式传入空列表

1
2
3
4
5
6
def main():
    zhangsan = Person("zhangsan", [])
    lisi = Person("lizi", [])
    zhangsan.frients.append("x")
    zhangsan.show_frients()
    lisi.show_frients()

这种方法可行,但每次创建实例都要写个[],失去了默认参数的意义。

方法2:修改类定义(推荐)

更好的方法是在类定义时就避免这个问题:

1
2
3
4
5
6
7
class Person:
    def __init__(self, name:str, frients:list|None=None):
        self.name = name
        if frients is None:
            self.frients = []
        else:
            self.frients = frients

这样: - 默认参数使用None(不可变对象) - 在__init__中根据情况创建新列表 - 每个实例都会得到自己独立的列表

PyCharm的贴心提示

如果你使用PyCharm,它会智能地识别出这个潜在问题并给出警告:

接受它的建议就能自动修复这个问题。

总结

这个"坑"其实体现了Python的一个设计选择:默认参数在函数定义时求值。对于不可变对象(如数字、字符串、元组)这不是问题,但对于可变对象(如列表、字典)就可能引发意外行为。

记住这个原则:永远不要用可变对象作为函数/方法的默认参数,用None代替并在函数内部初始化。

希望这篇文章能帮你避开这个Python新手常见的陷阱!