这章开始讨论Python面向对象的话题。

Python这种动态语言,变量本身是谈不上“类型”的,因为它可以随时指向另外的内容。本章一开头就提到这点,不能把变量看成“盒子”,而要看成“标签”。它跟静态语言正好是反的。另外,Python语言支持注解功能,可以标记函数参数的类型,但是,它是给IDE工具用的,并不影响语言的运行。

如果从标签来理解Python变量,很多事情就容易解释了。比如变量赋值,如果涉及对象,标签就使用引用处理了。相当于两个标签贴在了一个对象上,成了别名标识。

如果比较两个对象的值,用“==”运算符,因为它实际比较的是对象保存的数据,通过__eq__方法;而is则比较对象的标识,是否指向同一个对象,通过比较对象id。所以速度快得多。注意,Java语言的==定义可不一样。作者做了比较,认为Python的实现是更准确的。应用框架开发中,经常需要比较参数是否存在,正确的写法是跟None对比:

x is None

x is not None

Python定义了一类结构,称为“元组-tuple”。它一旦生成就是不可变的。但是精细的地方是:它的不可变性指的是tuple数据结构的物理内容 – 保存的引用不可变,而引用指向的对象,还是可变的。

什么意思?看例子就明白了。

t1 = (1, 4, [30, 50]) #内嵌列表

t1[-1].append(100) #合法操作

 

Python变量的标签语义,同时带来一个问题,就是复制变量的时候,默认是浅复制,如果被复制的对象包含引用,则只复制了引用,并没有复制引用指向的内容。如果不明白这一点,可能会导致意料不到的问题。学过C/C++指针概念的人,其实对此很容易理解。其它脚本语言,也有同样的设计,也面临一样的问题,比如PHP/JavaScript。

如果要理解这点,可以打开pythontutor.com,查看动态演示结果,效果做得很棒。

如果要做深复制,需要使用copy模块的deepcopy。对于JavaScript,可以使用lodash库的cloneDeep()方法。JavaScript因为独特的原型式对象设计,跟普通的“类-对象”模式不一样。对于PHP,可以使用clone操作,但是需要自定义对象的__clone方法,否则还是浅复制。此外,如果不计较方法复制,可以使用序列化+反序列化来复制对象。为什么深复制这么麻烦?因为可能有循环引用的问题。

Python的参数传递模式是共享传参 – 还是贴标签。这里面有一些很诡异、很容易导致Bug的地方:使用可变对象作为默认值传参;使用对象传参,内部修改传递到外部。

这节作者使用了一个例子来演示,这样做的严重后果。直接原因是,默认值在函数定义时计算,成了函数对象的属性,这个属性是共享的。那么对它的操作修改,就会影响其它对象的状态 – 不良涌现物。所以通常要使用None作为可变值的参数默认值。

对象传参的修改影响问题,需要确定这种行为是否可以接受,如果不能,那就必须在函数内部使用复制操作,重建一个对象,避免直接修改参数传入的对象。

最后一部分,作者讨论了del操作和弱引用,感觉编程中用的比较少。

如果对垃圾回收问题感兴趣,可以参考图灵公司出版的这本书:垃圾回收的算法与实现

 

流畅的Python-Fluent Python读书笔记-09-符合Python风格的对象
流畅的Python-Fluent Python读书笔记-07-函数装饰器和闭包