Python对象的比较和拷贝

李文轩 2019-08-17
声明:这是本人学习极客时间的Python核心技术与实战的笔记,有侵权请联系我。


  1. ' == ' VS ' is '
  2. 浅拷贝(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
# 在拷贝过程中,如果字典力已存在将要拷贝的对象,则会从字典直接返回。
# 这个方法有效提高拷贝效率以及防止无限递归的发生。

你可能感兴趣的:(Python对象的比较和拷贝)