Python 函数参数是可变对象为什么不好?

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更简练,看起来是更聪明的代码,但是其问题如下:

Python 函数参数是可变对象为什么不好?_第1张图片
Bus2 Error

tBus2并没有搭载任何乘客,但是其乘客列表里却出现了 tBus1上的乘客 Bob。这是为什么呢?这是因为在模块加载的时候,函数定义的默认值就已经创建对象保存在 __defaults__中了,如下图所示
Python 函数参数是可变对象为什么不好?_第2张图片

那么,任何空乘客列表实例化的 Bus2self.passengers都将成为之前函数定义时创建的默认列表的别名,也就是说共同指向同一个对象,就会出现之前图像中出现的问题。
Bus3相比 Bus1只在 __init__函数中的 self.passengers=passengers一句,而就是这么一句,造成了如下的错误,
Python 函数参数是可变对象为什么不好?_第3张图片
Bus3 Error

放到现实中,这是什么错误呢,校篮球队的 Alice乘坐了一趟校车,从校车上下来之后就被篮球队除名了, Alice很无奈。这个就是 Python按引用传递引起的问题,传入的 basketball_team里面的内容被修改了,因为 self.passengers参数被指定为传入的 passengers参数的别名,所以对 self.passengers也会引起 basketball_team的内容发生改变。
Bus1的实现中,使用 list()(浅)复制了 passengers对象,这样就可以避免出现这种问题了。此外,使用 list()还可以使得传入的参数多样化,可以是元组、集合或者其他的可迭代对象,并且保证了 pick()drop()方法中列表相关函数使用的合法性,可谓是非常完美。嗯,对了, Bus1这种编程方式叫做 防御式编程(defensive programming)
提一句 Bus4,是综合 Bus2Bus1给出的一个实现,小小测试了一下,并不会出现上述问题,会不会是一种好的实现呢?

One More Thing

Fluent Python在豆瓣上评分很高,果然奇书。另外,推荐一下它推荐的神奇网站Python Tutor,对于理解Python的内部机制很有用,而且这个网站是开源的,对应的源代码在github上。

感想

感觉高级语言的很多特性的理解还是是基于编译原理(还没有学)的,比如说引用和别名,是不是其实在CPU执行的时候,全部已经换成了对应对象的内存地址,对于CPU来说根本就不存在这些东西。

你可能感兴趣的:(Python 函数参数是可变对象为什么不好?)