目录
1.== 和 is
每个变量都有标识、类型和值。对象一旦创建,它的标识绝不会变。可以把标识理解为对象在内存中的地址。只有对象的值会不时变化。
变量保存的是引用,这一点对 Python 编程有很多实际的影响,影响有:
#1 简单的赋值不创建副本。 对 += 或 *= 所做的增量赋值来说,如果左边的变量绑定的是不可变对象,会创建新对象;
#2 如果是可变对象,会就地修改。 为现有的变量赋予新值,不会修改之前绑定的变量。这叫重新绑定:
#3 现在变量绑定了其他对象。如果变量是之前那个对象的最后一个引用,对象会被当作垃圾回收。 函数的参数以别名的形式传递,这意味着,函数可能会修改通过参 数传入的可变对象。这一行为无法避免,除非在本地创建副本,或者使用不可变对象(例如,传入元组,而不传入列表)。
#4 使用可变类型作为函数参数的默认值有危险,因为如果就地修改了 数,默认值也就变了,这会影响以后使用默认值的调用。
is 运算符比较两个对象的标识;id() 函数返回对象标识的整数表示。在 CPython 中,id() 返回对象的内存地址。标识最常使用 is 运算符检查,而不是直接比较 ID。
== 运算符比较两个对象的值(对象中保存的数据), is 比较对象的标识。
通常,我们关注的是值,而不是标识,因此 Python 代码中 == 出现的频率比 is 高。
is 运算符比 == 速度快,因为它不能重载,所以 Python 不用寻找并调用特殊方法,而是直接比较两个整数 ID。
a == b 是语法糖,等同于 a.__eq__(b)。多数内置类型使用更有意义的方式覆盖了 __eq__ 方法,考虑对象属性的值。
2.元组的相对不可变性
元组与多数 Python 集合(列表、字典、集,等等)一样,保存的是对象的引用。
元组的不可变性其实是指 tuple 数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。
不可变容器对象包含的对象集是不会改变的,但如果包含了对可变对象的引用,当后者的值改变时,该不可变容器对象的值也改变了。(不可变 ≠ 值不能变)
3.默认做浅复制
注意可以使用构造方法 list()、 [:]、copy.copy()进行浅层复制操作,但赋值语句增加对象的别名,指向的是同一个对象。
>>> s1 = {1, 2, 3}
>>> s2 = s1
>>> s2 is s1
True
>>> s3 = list(s1)
>>> s3 is s1
False
4.函数的参数作为引用时
Python 唯一支持的参数传递模式是共享传参(call by sharing),共享传参指函数的各个形式参数获得实参中各个引用的副本(即函数内部的形参是实参的别名),多数面向对象语言都采用这一模式。
示例:
>>> def s(a,b):
a += b
return a
>>> t1 = (1,2)
>>> t2 = (3,4)
>>> l1 = [1,2]
>>> l2 = [3,4]
>>> s(t1,t2)
(1, 2, 3, 4)
>>> s(l1,l2)
[1, 2, 3, 4]
>>> print(t1,t2,l1,l2)
(1, 2) (3, 4) [1, 2, 3, 4] [3, 4]
元组作为参数传给函数,之后元组的值没变;列表作为参数传给函数,之后列表的值发生变化。
可见函数可能会修改作为参数传入的可变对象,但是无法修改那些对象的标识(即不能把一个对象替换成另一个对象)。
4.1 不要使用可变类型作为参数的默认值
默认值在定义函数时计算(通常在加载模块时),因此默认值变成了函数对象的属性。因此,如果默认值是可变对象,而且修改了它的值,那么后续的函数调用都会受到影响
# 此节具体内容已省略
4.2 防御可变参数
通常使用 None 作为接收可变值的参数的默认值,__init__ 方法检查该参数的值是不是 None,如果是就赋值一个新的空列表。
5 del 语句和垃圾回收
del 语句解除该名称的绑定。名称的删除将从局部或全局命名空间中移除该名称的绑定,具体作用域的确定是看该名称是否有在同一代码块的 global
语句中出现。 如果该名称未被绑定,将会引发 NameError
。
在 CPython 中,垃圾回收使用的主要算法是引用计数,每个对象都会统计有多少引用指向自己。当引用计数归零时,对象立即就被销毁:CPython 会在对象上调用 __del__ 方法(如果定义了),然后释放分配给对象的内存。
CPython 2.0 增加了分代垃圾回收算法,用于检测引用循环中涉及的对象组——如果一组对象之间全是相互引用,即使再出色的引用方式也会导致组中的对象不可获取。Python 的其他实现有更复杂的垃圾回收程序,而且不依赖引用计数,这意味着,对象的引用数量为零时可能不会立即调用 __del__ 方法。
6 弱引用
弱引用不会增加对象的引用数量。引用的目标对象称为所指对象(referent)。因此我们说,弱引用不会妨碍所指对象被当作垃圾回收。
弱引用的主要用途是实现保存大对象的高速缓存或映射,但又并希望大对象仅仅因为它出现在高速缓存或映射中而保持存活。
class weakref.
ref
(object[, callback])
返回对 对象 的弱引用。
6.1 WeakValueDictionary
class weakref.
WeakValueDictionary
([dict])
弱引用值的映射类。 当不再有对键的强引用时字典中的条目将被丢弃。
WeakValueDictionary.
valuerefs
()
返回包含对值的弱引用的可迭代对象。
class weakref.
WeakSet
([elements])
保持对其元素弱引用的集合类。 当不再有对某个元素的强引用时元素将被丢弃。
6.2 弱引用的局限
不是每个 Python 对象都可以作为弱引用的目标(或称所指对象)。基本的 list 和 dict 实例不能作为所指对象,但是它们的子类可以。
set 实例可以作为所指对象。int 和 tuple 实例不能作为弱引用的目标,甚至它们的子类也不行。 这些局限基本上是 CPython 的实现细节,在其他 Python 解释器中情况可 能不一样。
7 Python对不可变类型施加的把戏
本节跳过
这里讨论的是 Python 的实现细节,对 Python 用户来说没那么重要。这些细节是 CPython 核心开发者走的捷径和做的优化措施,对这门语言的用户而言无需了解,而且那些细节对其他 Python 实现可能没用,CPython 未来的版本可能也不会用。