'==' VS 'is'
等于(==)和 is 是 Python 中对象比较常用的两种方式。简单来说,'=='操作符比较对象之间的值是否相等,比如下面的例子,表示比较变量 a 和 b 所指向的值是否相等。而'is'操作符比较的是对象的身份标识是否相等,即它们是否是同一个对象,是否指向同一个内存地址。
a = 10
b = 10
a == b
True
id(a)
4427562448
id(b)
4427562448
a is b
True
对于整型数字来说,以上a is b为 True 的结论,只适用于 -5 到 256 范围内的数字。这是因为出于对性能优化的考虑,Python 内部会对 -5 到 256 的整型维持一个数组,起到一个缓存的作用。这样,每次你试图创建一个 -5 到 256 范围内的整型数字时,Python 都会从这个数组中返回相对应的引用,而不是重新开辟一块新的内存空间。
一般当我们比较一个变量与一个单例(singleton)时,通常会使用'is'。一个典型的例子,就是检查一个变量是否为 None:
if a is None:
...
if a is not None:
...
比较操作符'is'的速度效率,通常要优于'=='。因为'is'操作符不能被重载。执行比较操作符'is',就仅仅是比较两个变量的 ID 而已。但是'=='操作符却不同,执行a == b相当于是去执行a.eq(b),而 Python 大部分的数据类型都会去重载eq这个函数,其内部的处理通常会复杂一些。比如,对于列表,eq函数会去遍历列表中的元素,比较它们的顺序和值是否相等。
注
- java中"=="进行的是数值比较,如果用于对象比较是比较两个内存的地址数值
- equals():是类所提供的的一个比较方法,可以直接进行字符串内容的判断
深拷贝与浅拷贝
先看栗子
l1 = [1, 2, 3]
l2 = list(l1) # l2是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
import copy
l1 = [1, 2, 3]
l2 = copy.copy(l1) # 还可以使用python提供的方法copy.copy
不过对于元组,使用 tuple() 或者切片操作符':'不会创建一份浅拷贝,相反,它会返回一个指向相同元组的引用:
t1 = (1, 2, 3)
t2 = tuple(t1)
t1 == t2
True
t1 is t2
True
浅拷贝,是指重新分配一块内存,创建一个新的对象,里面的元素是原对象中子对象的引用,因此,如果原对象中的元素不可变,那倒无所谓;但如果元素可变,浅拷贝通常会带来一些副作用,例如
l1 = [[1, 2], (30, 40)]
l2 = list(l1) # l2是l1的浅拷贝
l1.append(100) # 对 l1 的列表新增元素 100,这个操作不会对 l2 产生任何影响,因为 l2 和 l1 作为整体是两个不同的对象,并不共享内存地址
l1[0].append(3) # 因为 l2 是 l1 的浅拷贝,l2 中的第一个元素和 l1 中的第一个元素,共同指向同一个列表,因此 l2 中的第一个列表也会相对应的新增元素 3。操作后 l1 和 l2 都会改变
l1
[[1, 2, 3], (30, 40), 100]
l2
[[1, 2, 3], (30, 40)]
l1[1] += (50, 60) # 因为元组是不可变的,这里表示对 l1 中的第二个元组拼接,然后重新创建了一个新元组作为 l1 中的第二个元素,而 l2 中没有引用新元组,因此 l2 并不受影响
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)]
归纳
- 赋值:
在 Python 中,对象的赋值就是简单的对象引用,这点和 C++不同 - 浅拷贝(shallow copy):
浅拷贝会创建新对象,其内容非原对象本身的引用,而是原对象内第一层对象的引用。浅拷贝有三种形式:切片操作、工厂函数、copy 模块中的 copy 函数。 - 深拷贝(deep copy):
深拷贝只有一种形式,copy 模块中的 deepcopy()函数。深拷贝和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。因此,它的时间和空间开销要高。 - 拷贝的注意点:
1、对于非容器类型,如数字、字符,以及其他的“原子”类型,没有拷贝一说,产生的都是原对象的引用。
2、如果元组变量值包含原子类型对象,即使采用了深拷贝,也只能得到浅拷贝。
3、深浅拷贝操作可变数据类型时都是新开辟了一片内存,因此深浅拷贝后的地址和原地址都不一样