Python中的引用与复制简单示例
Python中的赋值=和函数传参用的是引用。 Python中的复制默认是浅复制,以示例表明浅复制与深复制之间的区别。
在==和is之间选择:
== 运算符比较两个对象的值(对象中保存的数据),而 is 比较对象的标识(内存地址)。 通常,我们关注的是值,而不是标识,因此 Python 代码中 == 出现的频率比 is 高。 然而,在变量和单例值之间比较时,应该使用 is。
检查变量绑定的值是不是 None。下面是推荐的写法:
x is None
x is not None
is 运算符比 == 速度快,因为它不能重载,所以 Python 不用寻找并调用特殊方法,而是直接比较两个整数 ID。 相等性测试可能涉及大量处理工作,例如,比较大型集合或嵌套层级深的结构时。
元组不可变的奥秘:
元组与多数 Python 集合(列表、字典、集,等等)一样,保存的是对象的引用。 如果引用的元素是可变的,即便元组本身不可变,元素依然可变。也就是说,元组的不可变性其实是指 tuple 数据结构的物理内容(即保存的引用)不可变,与引用的对象无关。
而 str、bytes 和 array.array 等单一类型序列是扁平的,它们保存的不是引用,而是在连续的内存中保存数据本身(字符、字节和数字)。
示例是最好的说明:
>>> t1 = (1, 2, [30, 40])
>>> t2 = (1, 2, [30, 40])
>>> t1 == t2
True
>>> id(t1[-1])
4431123592
>>> t1[-1].append(99)
>>> t1
(1, 2, [30, 40, 99])
>>> id(t1[-1])
4431123592
>>> t1 == t2
False
Python默认的复制是浅复制:
>>> l1 = [3, [55, 44], (7, 8, 9)]
>>> l2 = list(l1)
>>> l2
[3, [55, 44], (7, 8, 9)]
>>> l2 == l1
True
>>> l2 is l1
False
>>> l3 = l1[:]
>>> l3
[3, [55, 44], (7, 8, 9)]
>>> l1 == l3
True
>>> l1 is l3
False
构造方法或 [:] 做的是浅复制(即复制了最外层容器,副本中的元素是源容器中元素的引用)。如果所有元素都是不可变的,那么这样没有问题,还能节省内存。但是,如果有可变的元素,可能就会导致意想不到的问题。
为一个包含另一个列表的列表做浅复制:
>>> l1 = [3, [66, 55, 44], (7, 8, 9)]
>>> l2 = list(l1)
>>> l1.append(100)
>>> l1[1].remove(55)
>>> print('l1:', l1)
l1: [3, [66, 44], (7, 8, 9), 100]
>>> print('l2:', l2)
l2: [3, [66, 44], (7, 8, 9)]
>>> l2[1] += [33, 22]
>>> l2[2] += (10, 11)
>>> print('l1:', l1)
l1: [3, [66, 44, 33, 22], (7, 8, 9), 100]
>>> print('l2:', l2)
l2: [3, [66, 44, 33, 22], (7, 8, 9, 10, 11)]
copy模块中的copy是浅复制,deepcopy是深复制。 注意,一般来说,深复制不是件简单的事。如果对象有循环引用,那么这个朴素的算法会进入无限循环。deepcopy 函数会记住已经复制的对象,因此能优雅地处理循环引用。
b 引用 a,然后追加到 a 中;deepcopy 会想办法复制 a:
>>> a = [10, 20]
>>> b = [a, 30]
>>> a.append(b)
>>> a
[10, 20, [[...], 30]]
>>> from copy import deepcopy
>>> c = deepcopy(a)
>>> c
此外,深复制有时可能太深了。例如,对象可能会引用不该复制的外部资源或单例值。我们可以实现特殊方法 __copy__()
和 __deepcopy__()
,控制 copy 和 deepcopy 的行为,详情参见 copy 模块的文档。
整理自《流畅的Python》第8章相关内容。