Python之一个参数引发的血案

参数为可变对象引发的问题

先来看一个结果符合绝大多数人预期的例子🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
class Company:
def __init__(self, name: str, staffs: list=[]):
self.name: str = name
self.staffs: list = staffs

def add(self, staff_name: str):
self.staffs.append(staff_name)


if __name__ == "__main__":
co = Company("xx", ["A", "B"])
co.add("C")
print(co.staffs)

执行结果:

1
['A', 'B', 'C']

这个结果想必大家都能猜得到, 接下来看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Company:
def __init__(self, name: str, staffs: list=[]):
self.name: str = name
self.staffs: list = staffs

def add(self, staff_name: str):
self.staffs.append(staff_name)


if __name__ == "__main__":
co1 = Company("yy")
co1.add("D")
print("co1's staffs: {0}".format(co1.staffs))

co2 = Company("oo")
co2.add("E")
print("co1's staffs: {0}".format(co1.staffs))
print("co2's staffs: {0}".format(co2.staffs))

执行结果:

1
2
3
co1's staffs: ['D']
co1's staffs: ['D', 'E']
co2's staffs: ['D', 'E']

第一行的结果, 应该是符合大家的预期的, 但是第二行第三行的结果可以看出, 虽然是两个完全不同的对象, 但是互相受到了影响! 对co2添加员工的操作影响了co1中的成员, 而最开始co1中添加成员的操作也默认影响到了co2的员工列表

这是为什么呢?

如果你用的PyCharm IDE在你定义函数/方法参数的时候, 如果定义的默认参数为列表, 那么IDE会提示你: Default argument value is mutable, 不推荐在函数的参数中直接接收列表类型的数据(不推荐使用可变对象).

那么抛开建议, 为什么会出现上面两个对象之间的值互相影响的情况呢?

是因为co1co2两个对象在创建的时候, 都没有明确指定staffs列表, 而是都使用了默认的空列表, 在这种情况下他们实际就共用了一个对象, 而恰巧该对象又是可变对象, 所以创建对象后, 任意一个对象对该可变对象的操作都会影响到另一个对象, 在Python代码中, 可以使用is语句来验证两个对象的两个字段是否都引用了同一个对象

1
print(co1.staffs is co2.staffs)

执行结果:

1
True

验证结果也明确显示了, 两个对象的两个字段, 其实是引用了同一个对象, 同一块内存, 我们也可以通过特定函数查看类中构造方法默认值的使用情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Company:
def __init__(self, name: str, staffs: list=[]):
self.name: str = name
self.staffs: list = staffs

def add(self, staff_name: str):
self.staffs.append(staff_name)


if __name__ == "__main__":
print(Company.__init__.__defaults__) # ([],)
co1 = Company("yy")
co1.add("D")
print("co1's staffs: {0}".format(co1.staffs)) # co1's staffs: ['D']
print(Company.__init__.__defaults__) # (['D'],)
co2 = Company("oo")
co2.add("E")
print("co1's staffs: {0}".format(co1.staffs)) # co1's staffs: ['D', 'E']
print("co2's staffs: {0}".format(co2.staffs)) # co2's staffs: ['D', 'E']
print(Company.__init__.__defaults__) # (['D', 'E'],)
print(co1.staffs is co2.staffs) # True

当函数的参数没有指定值的时候, 就会指向Company.__init__.__defaults__这里的默认值

如果解决这类问题

  • 第一: 最直接的解决之道就是不要使用列表和字典这种可变对象作为参数传递
  • 第二: 可以使用元组传递
  • 第三: 参数的默认值不要使用列表或字典, 让调用者每次都显式传递参数
  • 第四: 使用 def func(args, *kwargs) 的方式传递