使用可变对象做为python函数默认参数引起的问题

写python的都知道,python函数或者方法能够使用默认参数,好比python

1 def foo(arg=None):
2     print(arg)
3  
4 foo()
5   
6 foo("hello world")

 

一个很简单的函数,参数arg默认使用None,当调用foo函数时,能够传入一个参数,也能够不传入参数,运行结果以下app

None
hello world

 

这很好理解。默认参数是python一个很好的特性。函数

 

可是若是使用可变对象做为默认参数,就会引起问题。以前写过一个脚本,bug不断,后来终于找到了缘由。下面引用Fluent python中的一个例子加以说明。spa

废话很少说,直接上代码code

 1 """可变对象做为默认参数传入到函数引起的问题"""
 2 
 3 
 4 class Bus:
 5     def __init__(self, passengers=[]):
 6         self.passengers = passengers
 7 
 8     def pick(self, a_passenger):
 9         self.passengers.append(a_passenger)
10 
11     def drop(self, a_passenger):
12         self.passengers.remove(a_passenger)
13 
14 
15 if __name__ == '__main__':
16     bus1 = Bus(['Alice', 'Jeff', 'ethan'])
17     print(bus1.passengers)
18     bus1.pick("Simon")
19     print(bus1.passengers)  # 到此为止并没什么问题
20 
21     # 接下来问题来了, 建立了两个Bus对象,并进行操做。
22     bus2 = Bus()
23     print(bus2.passengers)
24     bus2.pick("Alice")   # "Alice"上了bus2
25     print(bus2.passengers)  # 打印bus2的乘客列表
26     bus3 = Bus()   # 建立另外一个Bus实例,一样不传入参数(使用默认[]参数)
27     print(bus3.passengers)  # 打印bus3的乘客列表
28 
29     bus3.pick("alex")
30     print(bus3.passengers)
31     print(bus2.passengers)
32     print(bus3.passengers is bus2.passengers)
33 
34     print(bus3.passengers is bus1.passengers)

 

下面来看运行结果对象

['Alice', 'Jeff', 'ethan']
['Alice', 'Jeff', 'ethan', 'Simon'] [] ['Alice'] ['Alice'] ['Alice', 'alex'] ['Alice', 'alex'] True False

 

从结果能够看出,bus1这个对象彷佛没什么问题,而bus2和bus3对象的passengers属性彷佛是同一个。为何会这样呢?blog

咱们先来分析下Bus这个类rem

定义了一个Bus类,有一个__init__方法,pick方法和drop方法。__init__方法有一个默认参数,即passengers=[]。it

你们应该都知道在python中列表对象([])是可变对象。 class

当建立bus1对象的时候,传入了实实在在的passengers参数(一个非空列表)

当建立bus2和bus3对象的时候,没有传入参数,因此会使用默认参数([])

而后我对这几个Bus实例进行操做(调用pick或drop方法,即插入或者删除passengers列表中的乘客)

从结果能够很直观的看出来,bus1.passengers 并没什么问题,一切按照咱们的预期进行。可是bus2的乘客"Alice"为何会出如今bus3上? bus3的乘客“alex”为何又会出如今bus2上???

 

其实这就是用可变对象做为默认参数引起的后果。由于当使用默认参数定义了一个函数/方法以后,加载模块的时候已经初始化了这个可变对象,因此当你调用这个函数/方法的时候,若是你未传入参数(即便用了默认参数), 则对函数/方法的不一样调用,其实传入的是同一个可变对象(上面例子中是一个空列表)。 

下面请看

 1 class Bus:
 2     def __init__(self, passengers=[]):
 3         self.passengers = passengers
 4  
 5     def pick(self, a_passenger):
 6         self.passengers.append(a_passenger)
 7  
 8     def drop(self, a_passenger):
 9         self.passengers.remove(a_passenger) 
10 
11 if __name__ == '__main__':
12     print(Bus.__init__.__defaults__)

 

 

运行结果

([],)

 

在这个例子中,并无初始化Bus类, 而__init__方法的__defaults__属性,是一个空列表, 也就是说,在完成__init__方法定义的时候,就已经初始化了默认参数(一个空列表), 由于列表是可变对象,因此后续对Bus类的引用就会引起问题。

至此,问题缘由很明确了,就是可变对象致使了这个问题,所以千万不要使用可变对象做为python函数/方法的默认参数, 通常建议使用None做为默认参数,即
1 def foo(arg=None):
2      pass
相关文章
相关标签/搜索