Fluent Python: Mutable Types as Parameter Defaults: Bad Idea

  在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)

这里有一个原则,如果定义的函数接收可变参数,应该谨慎考虑调用方是否期望修改传入的参数。

上一篇:Vue学习之路2-项目初搭建


下一篇:Java AES加密案例