Source
《Fluent Python》chapter 8
总结
先上代码
class Bus1:
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)
class Bus2:
def __init__(self, passengers=[]):
self.passengers = passengers
def pick(self, name):
self.passengers.append(name)
def drop(self, name):
self.passengers.remove(name)
class Bus3:
def __init__(self, passengers=None):
if passengers is None:
self.passengers = [ ]
else:
self.passengers = passengers
def pick(self, name):
self.passengers.append(name)
def drop(self, name):
self.passengers.remove(name)
class Bus4:
def __init__(self, passengers=[]):
self.passengers = list(passengers)
def pick(self, name):
self.passengers.append(name)
def drop(self, name):
self.passengers.remove(name)
上面的代码实现了几个校车类别, 但是只有Bus1是最优实现,Bus2, Bus3都有各自的问题。
Bus2的代码比Bus1更简练,看起来是更聪明的代码,但是其问题如下:

Bus2 Error
tBus2并没有搭载任何乘客,但是其乘客列表里却出现了tBus1上的乘客Bob。这是为什么呢?这是因为在模块加载的时候,函数定义的默认值就已经创建对象保存在__defaults__中了,如下图所示
那么,任何空乘客列表实例化的
Bus2的self.passengers都将成为之前函数定义时创建的默认列表的别名,也就是说共同指向同一个对象,就会出现之前图像中出现的问题。Bus3相比Bus1只在__init__函数中的self.passengers=passengers一句,而就是这么一句,造成了如下的错误,
Bus3 Error
放到现实中,这是什么错误呢,校篮球队的
Alice乘坐了一趟校车,从校车上下来之后就被篮球队除名了,Alice很无奈。这个就是Python按引用传递引起的问题,传入的basketball_team里面的内容被修改了,因为self.passengers参数被指定为传入的passengers参数的别名,所以对self.passengers也会引起basketball_team的内容发生改变。而
Bus1的实现中,使用list()(浅)复制了passengers对象,这样就可以避免出现这种问题了。此外,使用list()还可以使得传入的参数多样化,可以是元组、集合或者其他的可迭代对象,并且保证了pick()和drop()方法中列表相关函数使用的合法性,可谓是非常完美。嗯,对了,Bus1这种编程方式叫做防御式编程(defensive programming)。提一句
Bus4,是综合Bus2和Bus1给出的一个实现,小小测试了一下,并不会出现上述问题,会不会是一种好的实现呢?
One More Thing
Fluent Python在豆瓣上评分很高,果然奇书。另外,推荐一下它推荐的神奇网站Python Tutor,对于理解Python的内部机制很有用,而且这个网站是开源的,对应的源代码在github上。
感想
感觉高级语言的很多特性的理解还是是基于编译原理(还没有学)的,比如说引用和别名,是不是其实在CPU执行的时候,全部已经换成了对应对象的内存地址,对于CPU来说根本就不存在这些东西。