在Fluent Python一书第八章有一个示例,未看书以先很难理解这个示例运行的结果,我们先看结果,然后再分析问题原因:
定义了如下Bus类:
class Bus:
def __init__(self, passengers=[]):
self.passengers = passengers def pick(self, name):
self.passengers.append(name) def drop(self, name):
self.passengers.remove(name)
创建两个Bus 类实例,bus1, bus2
>>> import Example8_12
>>> bus1 = Example8_12.Bus()
>>> bus2 = Example8_12.Bus()
假如bus1接到一个一名乘客Alice:
>>> bus1.pick('Alice')
此时我们看看bus2里的乘客:
>>> bus2.passengers
['Alice']
bus2本应该是空的,但是此时却有bus1 pick的乘客'Alice', 这是什么原因呢?
出现这个问题的根源是:默认值在定义函数时计算(通常是在import 模块时),并且默认值变成了函数对象的属性:
我们可以省察下类Bus __init__方法的属性
>>> dir(Example8_12.Bus.__init__)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
留意其中有个__defaults__属性,我们看看其中的内容:
>>> Example8_12.Bus.__init__.__defaults__
(['Alice'],)
我们可以看到'Alice'正在其中,这也是为什么bus2里会有乘客'Alice'的原因.
我们可以验证bus2.passengers是一个别名,它绑定到Bus1.__init__.__defaults__属性的第一个元素上了:
>>> Example8_12.Bus.__init__.__defaults__[0] is bus2.passengers
True
这个示例说明了为什么通常使用None作为接收可变值的参数的默认值。
这个类正确的书写应该如下:
class Bus:
def __init__(self, passengers=None):
if passengers is None:
self.passengers = []
else:
self.passengers = list(passengers) def pick(self, name):
self.passengers.append(name) def drop(self, name):
self.passengers.remove(name)
这里有一个原则,如果定义的函数接收可变参数,应该谨慎考虑调用方是否期望修改传入的参数。