李文轩 2019-08-17
声明:这是本人学习极客时间的Python核心技术与实战的笔记,有侵权请联系我。
- ' == ' VS ' is '
- 浅拷贝(shallow copy)与深拷贝(deep copy)
' == ' VS ' is '
==
和is
都是Python中对象比较常用的两种方式。==
操作符会比较两个变量的值是否相等;而 is
操作符则会比较两个对象的 ID 是否相等。(Python 中,可以通过 id(object)
的函数获得每个对象的身份标识。)
例子:
# 整数作为例子的对象比较
a = 10
b = 10
a == b
# 输出 # True
id(a)
4427562448
id(b)
4427562448
a is b
# 输出 # True
在整数变量的比较中,如果两个值相同的变量在[-5, 256]
的范围外的话,==
会依旧返回True,因为值是相同的。is
不会返回True,因为在[-5, 256]
范围外的整数,不管如何,Python都会分别为值一样的整数开辟一段新的内存。范围内的整数则是用数组维持,作为缓存使用;所以在范围内的整数都不会重新开辟一块新的内存空间。
例子:
# [-5, 256] 范围外的整数比较
a = 257
b = 257
a == b
# 输出 # True
id(a)
4473417552
id(b)
4473417584
a is b
# 输出 # False
is
不可以被重载,而==
可以。重载==
的方法是重载对象的__eq__
函数。例如,对于列表,__eq__
函数会去遍历列表中的元素,比较它们的顺序和值是否相等。
对于不可变(immutable)的变量,==
或者 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)
# 如果是序列,切片也能达成:l2 = l1[:]
# copy.copy()函数也可以:l2 = copy.copy(l1)
l2
# 输出 # [1, 2, 3]
l1 == l2
# 输出 # True
l1 is l2
# 输出 # False
# 两个不同的对象,在两个不同的地址
# 浅拷贝的操作(集合)
s1 = set([1, 2, 3])
s2 = set(s1)
s2
# 输出 # {1, 2, 3}
s1 == s2
# 输出 # True
s1 is s2
# 输出 # False
# 两个不同的对象,在两个不同的地址
元组例子:
# 浅拷贝的操作(元组)
t1 = (1, 2, 3)
t2 = tuple(t1)
t1 == t2
# 输出 # True
t1 is t2
# 输出 # True
# 对于元组和字典,使用函数或者切片并不能创建一份浅拷贝;
# 它只会返回一个指向相同元组的引用
对于原对象中的不可变元素,浅拷贝是没有问题的。对于原对象中的可变元素,在改变原对象的这些元素时,拷贝对象的元素也会跟着改变。
# 浅拷贝中的可变与不可变变量
l1 = [[1, 2], (30, 40)]
l2 = list(l1)
l1.append(100)
l1[0].append(3)
l1
# 输出 # [[1, 2,3], (30, 40), 100]
l2
# 输出 # [[1, 2,3], (30, 40)]
# 即使 l1 与 l2 是两个不同的对象,
# 可是第一个元素(列表)是指向同一个地址的同一个对象,
# 所以在改变 l1[0] 时,也会改变 l2[0]
l1[1] += (50, 60)
l1
# 输出 # [[1, 2,3], (30, 40, 50, 60), 100]
l2
# 输出 # [[1, 2,3], (30, 40)]
# 元组是不可变的对象,所以为 l1[1] 增加整数时
# 实际上是生成了一个新的元组来替换 l1[1]
# l2[1] 不会改变,l1[1] 和 l2[1] 指向了不同的对象。
深拷贝是指,重新分配一块内存,创建一个新的对象,并且将原来对象中的元素以递归的方式,通过创建新的子对象拷贝到新对象中。自此,新对象和原对象没有任何关联。
#深拷贝的例子
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)]
在使用深拷贝时,如果被拷贝对象中存在指向自身的引用,程序会容易地陷入无限循环:
# 深拷贝的无限循环
import copy
x = [1]
x.append(x)
x
[1, [...]]
y = copy.deepcopy(x)
y
[1, [...]]
# 列表 x 中有指向自身的引用
# x 是一个无限嵌套的列表
# 但是深度拷贝后,并没有发生 stack overflow 的现象
# 原因是 deepcopy 函数中会维护一个字典,记录已拷贝的对象与其ID
# 在拷贝过程中,如果字典力已存在将要拷贝的对象,则会从字典直接返回。
# 这个方法有效提高拷贝效率以及防止无限递归的发生。