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

Bus2 Error

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

那么,任何空乘客列表实例化的Bus2self.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,是综合Bus2Bus1给出的一个实现,小小测试了一下,并不会出现上述问题,会不会是一种好的实现呢?

One More Thing

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

感想

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

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,455评论 25 708
  • 关注成长梅一天,专注目标与个人成长。和你一起探索未来,找到自己成长的方向,过上向往的生活~ 本文1400字...
    成长每义天阅读 178评论 0 0
  • 关于构图的核心法则: 跟写文章措辞要精炼一样,摄影构图也要精炼,画面中的其他元素一定要凸显、围绕主题(拍摄对象)、...
    账房先森阅读 258评论 0 0