Python中的比较和拷贝

Python中的比较和拷贝

‘==’和’is’

’==‘操作符比较对象之间的值是否相等,’is’操作符比较的是对象的身份标识是否相等,即是否是同一个对象,是否指向同一个内存地址。

在Python中,可以使用id(object)函数获得每个对象的身份表示。因此,is操作符相当于比较对象之间的ID是否相等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = 10
b = 10

a == b
True

id(a)
4427562448

id(b)
4427562448

a is b
True

ab指向同一片内存的原因在于,Python会首先给10分配一块内存区域,接着将ab都指向这块内存。因此,两者的值和内存地址都相同。

但是当整型数据不在[-5,256]范围内时,结果会是值相同而内存不相同,这一差异是由Python的内存管理机制导致的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a = 257
b = 257

a == b
True

id(a)
4473417552

id(b)
4473417584

a is b
False

实际操作时,更多使用==来比较两个对象的值是否相同,is操作符常用于比较一个变量与一个单例:

1
2
3
4
5
if a is None:
...

if a is not None:
...

is操作符的效率高于==,因为is操作符不会被重载。而==操作是通过执行对象的a.__eq__(b)函数来执行比较操作的,不同的类会重载不同的__eq__()函数,因而效率相对低一点。

浅拷贝和深度拷贝

常见的浅拷贝是使用数据类型本身的构造器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
l1 = [1, 2, 3]
l2 = list(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

也可以使用copy.copy()函数进行浅拷贝操作。但是对于元组,使用tuple()或者切片操作符:不会创建一份浅拷贝,而是返回一个指向相同元组的引用:

1
2
3
4
5
6
7
8
t1 = (1, 2, 3)
t2 = tuple(t1)

t1 == t2
True

t1 is t2
True

那么,浅拷贝就指的是重新分配一片内存,创建一个新的对象,里面的元素是原对象中的元素的引用。如果原始对象的元素不可变,影响不大;但如果元素可变,通常会带来一些副作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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[1] += (50, 60)
l1
[[1, 2, 3], (30, 40, 50, 60), 100]

l2
[[1, 2, 3], (30, 40)]

如果向避免上面的副作用就需要使用深度拷贝。

深度拷贝指的是:重新分配一块内存,创建一个新的对象,并对原对象中的元素以递归的方式创建新的子对象拷贝到新对象中,因而,新对象和原对象没有任何关联

深度拷贝的实现方式如下:

1
2
3
4
5
6
7
8
9
10
11
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)]

但如果原始对象中存在到自身的引用,在使用深度拷贝时很容易出现无限循环。

1
2
3
4
5
6
7
8
9
10
import copy
x = [1]
x.append(x)

x
[1, [...]]

y = copy.deepcopy(x)
y
[1, [...]]

但在执行时并未出现溢出的现象,这时应为深度拷贝函数会在拷贝的同时维持一个列表记录已经被拷贝的对象和其ID,遇到已经拷贝过的对象,会直接从字典中返回。

参考