使用可变对象作为python函数默认参数引发的问题
写python的都知道,python函数或者方法可以使用默认参数,比如
1 def foo(arg=None): 2 print(arg) 3 4 foo() 5 6 foo("hello world")
一个很简单的函数,参数arg默认使用None,当调用foo函数时,可以传入一个参数,也可以不传入参数,运行结果如下
1 None 2 hello world
这很好理解。默认参数是python一个很好的特性。
但是如果使用可变对象作为默认参数,就会引发问题。之前写过一个脚本,bug不断,后来终于找到了原因。下面引用Fluent python中的一个例子加以说明。
废话不多说,直接上代码
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属性似乎是同一个。为什么会这样呢?
我们先来分析下Bus这个类
定义了一个Bus类,有一个__init__方法,pick方法和drop方法。__init__方法有一个默认参数,即passengers=[]。
大家应该都知道在python中列表对象([])是可变对象。
当创建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 12 if __name__ == '__main__': 13 print(Bus.__init__.__defaults__)
运行结果
([],)
在这个例子中,并没有初始化Bus类, 而__init__方法的__defaults__属性,是一个空列表, 也就是说,在完成__init__方法定义的时候,就已经初始化了默认参数(一个空列表), 因为列表是可变对象,所以后续对Bus类的引用就会引发问题。
至此,问题原因很明确了,就是可变对象导致了这个问题,因此千万不要使用可变对象作为python函数/方法的默认参数, 一般建议使用None作为默认参数,即
1 def foo(arg=None): 2 pass
posted on 2018-05-06 22:30 hz_pythoner 阅读(127) 评论(0) 编辑 收藏 举报