对象比较与拷贝
比较一个变量与一个单例(singleton)时,通常会使用'is'。
'=='操作符比较对象之间的值是否相等
比较操作符'is'的速度效率,通常要优于'=='。
元组是不可变的,但元组可以嵌套,它里面的元素可以是列表类型,列表是可变的,所以如果我们修改了元组中的某个可变元素,那么元组本身也就改变了,之前用'is'或者'=='操作符取得的结果,可能就不适用了。
这一点在日常写程序时一定要注意,在必要的地方请不要省略条件检查。
t1 = (1, 2, [3, 4])
t2 = (1, 2, [3, 4])
t1 == t2
True
t1[-1].append(5)
t1 == t2
False
l1 = [1, 2, 3]
l2 = list(l1) # 2 就是 l1 的浅拷贝
l2
[1, 2, 3]
l1 == l2
True
l1 is l2
False
s1 = set([1, 2, 3])
s2 = set(s1) # s2 是 s1 的浅拷贝
s2
{1, 2, 3}
s1 == s2
True
s1 is s2
False
对于可变的序列还可以通过切片操作符':'完成浅拷贝
l1 = [1, 2, 3]
l2 = l1[:]
l1 == l2
True
l1 is l2
False
Python 中也提供了相对应的函数 copy.copy(),适用于任何数据类型
import copy
l1 = [1, 2, 3]
l2 = copy.copy(l1)
对于元组,使用 tuple() 或者切片操作符':'不会创建一份浅拷贝
相反,它会返回一个指向相同元组的引用
t1 = (1, 2, 3)
t2 = tuple(t1)
t1 == t2
True
t1 is t2
True
# 元组 (1, 2, 3) 只被创建一次,t1 和 t2 同时指向这个元组
浅拷贝,是指重新分配一块内存,创建一个新的对象,里面的元素是原对象中子对象的引用。
因此,如果原对象中的元素不可变,那倒无所谓;但如果元素可变,浅拷贝通常会带来一些副作用,尤其需要注意。
l1 = [[1, 2], (30, 40)]
l2 = list(l1)
l1.append(100)
l1[0].append(3)
# l2 中的元素和 l1 指向同一个列表和元组对象。
l1
[[1, 2, 3], (30, 40), 100]
# 对 l1 的列表新增元素 100。这个操作不会对 l2 产生任何影响
# 因为 l2 和 l1 作为整体是两个不同的对象,并不共享内存地址。
l2
[[1, 2, 3], (30, 40)]
# 因为元组是不可变的,这里对 l1 中的第二个元组拼接,然后重新创建了一个新元组作为 l1 中的第二个元素,而 l2 中没有引用新元组,因此 l2 并不受影响。
l1[1] += (50, 60)
l1
[[1, 2, 3], (30, 40, 50, 60), 100]
l2
[[1, 2, 3], (30, 40)]
Python 中以 copy.deepcopy() 来实现对象的深度拷贝,新对象和原对象没有任何关联。
import copy
l1 = [[1, 2], (30, 40)]
l2 = copy.deepcopy(l1)
l1.append(100)
l1[0].append(3)
l1
[[1, 2, 3], (30, 40), 100]
l2
[[1, 2], (30, 40)]
如果被拷贝对象中存在指向自身的引用,那么程序很容易陷入无限循环。
深度拷贝函数 deepcopy 中会维护一个字典,记录已经拷贝的对象与其 ID。拷贝过程中,如果字典里已经存储了将要拷贝的对象,则会从字典直接返回。防止无限递归。
浅拷贝,不可变的不可变,可变的依旧可变 深拷贝,都不可变。
网友评论