Python中的对象(四)
by Harrison Feng in Python
在上一篇文章《Python中的对象(三)》中,我们探讨了Python对象的mutability,今天我们要分享的是复制对象和比较两个对象。
在《Python中的对象(三)》中,LIST1和LIST2引用了同一个mutable对象,所以当mutable的对象改变,LIST1和LIST2也跟着变
化。这样的行为可能是你的程序所需的,但是有时候,可能你不希望这样。你想要的是LIST1和LIST2的变化互相不影响。这个时
候,要用到的就是对象的复制。
1、复制对象
a. 隐式复制
我们先来开一段代码:
-
>>> LIST1 = [1, 2, 3]
-
>>> LIST2 = LIST1[:] # 列表的切片(slicing)功能
-
>>> id(LIST1), id(LIST2)
-
(4293721356L, 4293727372L)
-
>>> LIST1[0] = 0
-
>>> LIST1
-
[0, 2, 3]
-
>>> LIST2
-
[1, 2, 3]
我们用列表的切片(slicing)功能生成一个和LIST1指向的对象一样的对象,其实就是复制了对象。很明显,LIST1和LIST2指向的
对象有不同的identity,即它们分别放在了不同内存地址里。所以LIST1和LIST2指向的是两个不同的的对象,只是此刻它们的值
是一样的。当我们改变其中一个的值时,另一个对象不会受到影响, 即不会随着改变
【1】 。过程如图1所示。
图 1. 隐式复制
从图中我们可以看出,当执行LIST1[0] = 0时,只是使LIST1的第一个元素指向0,LIST2的元素的绑定(引用)并没有变。我们之
所以称它为隐式复制,是因为列表切片(slicing)操作复制了一个同值的对象。
b. 显示复制
我们也可以用Python的标准库的copy模块来复制一个对象。它有两种方法,一种是浅拷贝(shallow copy),一种是深拷贝(deep
-
import copy
-
-
x = copy.copy(y) # make a shallow copy of y
-
x = copy.deepcopy(y) # make a deep copy of y
copy)。这两种拷贝方法仅当被拷贝的对象是复合对象
【2】时,它们会有不同。如果被拷贝对象是非复合对象,那么两种拷贝方法
是一样的,且不会真正的复制对象。
-
>>> a = 1
-
>>> b = copy.copy(a)
-
>>> b
-
1
-
>>> id(a), id(b)
-
(31284744L, 31284744L)
>>> c = copy.deepcopy(a)
-
>>> c
1
>>> id(a), id(b), id(c)
(31284744L, 31284744L, 31284744L)
a,b,c有相同的identity,都是31284744L,所以a,b, c指向同一个对象。
下面的例子解释了两种方法的不同。
b.1. 浅拷贝(shallow copy)
-
>>> L1 = [[1,2], ['a', 'b']]
-
>>> L2 = copy.copy(L1)
-
>>> id(L1), id(L2)
-
(39824136L, 39847240L) # L1和L2分别指向两个不同的对象
-
>>> L1
-
[[1, 2], ['a', 'b']]
-
>>> L2
-
[[1, 2], ['a', 'b']] # L2和L2的值是一样的,因为我们复制了对象。
-
>>> L2[0][0] = 0 # 改变L2指向的第一个对象的第一个元素的值。
-
>>> L1
-
[[0, 2], ['a', 'b']]
-
>>> L2
-
[[0, 2], ['a', 'b']] # L1和L2指向的对象的值同时都改变了。
如果你试着改变L1指向的对象的元素值,效果是一样的。读者可以自行测试。shallow copy过程如图2所示:
图 2. Shallow Copy
从图中可以看出,shallow copy只是复制被拷贝对象的第一级(top level)对象。所以,对于第二级对象仍然是引用的关系。因此,
当我们改变第二级对象的值时,两个对象的值都会同时改变。
b2. 深拷贝(deep copy)
-
>>> import copy
-
>>> L1 = [[1, 2], ['a', 'b']]
-
>>> L2 = copy.deepcopy(L1)
-
>>> id(L1), id(L2)
-
(40001608L, 40004488L) # L1和L2分别指向两个不同的对象
-
>>> L1
-
[[1, 2], ['a', 'b']]
-
>>> L2
-
[[1, 2], ['a', 'b']] # L2和L2的值是一样的,因为我们复制了对象。
-
>>> L1[0][0] = 0 # 改变L1指向的第一个对象的第一个元素的值。
-
>>> L1
-
[[0, 2], ['a', 'b']]
-
>>> L2
-
[[1, 2], ['a', 'b']] # L2指向的对象的值并没有变化。
当L1指向的对象的第二级对象的元素变化时,对L2并没有影响。如果你试着改变L2指向的对象,也会获得同样的结果。Deep copy
的过程如图3所示:
图 3. Deep Copy
从图中可以看出,整个对象的结构被复制了,其实就是递归复制。
至此,Shallow Copy和Deep Copy的区别已经变得很清楚了。在实际的开发项目中,我们应该根据需要来选择对象复制的方法。
2、比较对象
在实际应用中,我们有时候需要比较两个对象。Python中比较两个对象涉及到两种方式: “==” 和 “is”。
“==”比较是测试两个被引用的对象的值是否相等。 看下面的代码块1,
代码块1
-
>>> L1 = [1, 2, 3]
-
>>> L2 = [1, 2, 3]
-
>>> id(L1), id(L2)
-
(40080968L, 40082824L)
-
>>> L1 == L2
-
True
>>> L1 is L2
False
-
>>>
从上面的代码看,很显然L1和L2指向了不同的对象。但是这两个对象的值是相等。所以“==”比较的结果是True。
“is” 比较是测试两个对象是否是同一个对象。看下面的代码块2,
代码块2
-
>>> L1 = [1, 2, 3]
-
>>> L2 = L1
-
>>> id(L1), id(L2)
-
(40082824L, 40082824L)
-
>>> L2 is L1
-
True
-
>>> L2 == L1
True
-
>>>
从上面的代码看,很显然L1和L2指向了同一个对象,因此L1和L2实际上就是同一个对象。所以“is”比较的结果是True。
实际上,“is”比较的是两个对象的identity(通过id()获取的一个整数值),如果identity相等则为True,否则则为False。
实际上在Python中,如果identity相等,必然是同一个对象。所以,在代码块2中“L1 is L2”的结果是True,在代码块1中
”L1 is L2“的结果是False。而 ”==“比较的仅仅是对象的值。同一个对象,值必然相等,不同对象,它们的值有可能相等,
有可能不相等。也就是说
如果”a is b"是True,那么“a == b”必定为True;
如果”a is b"是False,那么“a == b“可能为True或者False。
所以,我们可以把”==“看做是弱比较,把”is“看做是强比较。
由于Python不会重复创建非复合对象,一旦一个非复合对象被创建,它将会被引用数次,直到被垃圾回收掉。我们可以
用sys.getrefcount()来获得所给对象被引用的次数。
-
>>> import sys
-
>>> sys.getrefcount(True)
-
31
-
>>> sys.getrefcount(0)
-
172
-
>>>
至此,《Python中的对象》系列已全部分享完毕。Python这门语言最初确实是一门脚本语言。但是发展到现在,Python
已经远远超越了脚本语言的范畴。Python已经渗透到了软件开发的各个领域,并且越来越流行,越来越火了。学习Python,
使用Python,享受Python吧!
注:
【1】 前提是对象的值是非复合对象,如果是复合对象,其实也是Shallow Copy。
【2】 可以包含其他对象的对象叫复合对象。例如: 列表,字典,类实例等。
by Harrison Feng in
Python
阅读(5818) | 评论(0) | 转发(1) |