写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