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
更简练,看起来是更聪明的代码,但是其问题如下:
tBus2
并没有搭载任何乘客,但是其乘客列表里却出现了tBus1
上的乘客Bob
。这是为什么呢?这是因为在模块加载的时候,函数定义的默认值就已经创建对象保存在__defaults__
中了,如下图所示那么,任何空乘客列表实例化的
Bus2
的self.passengers
都将成为之前函数定义时创建的默认列表的别名,也就是说共同指向同一个对象,就会出现之前图像中出现的问题。Bus3
相比Bus1
只在__init__
函数中的self.passengers=passengers
一句,而就是这么一句,造成了如下的错误,放到现实中,这是什么错误呢,校篮球队的
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来说根本就不存在这些东西。