15 | Python對象的比較、拷貝

目錄

 

1.回顧

2.深拷貝

3.總結

4.思考


1.回顧

在前面的課中,已經學許多python對象比較和複製的例子

if a == b:
    ...

這裏的l2就是l1拷貝得到的。

l1 = [1, 2, 3]
l2 = list(l1)

l2是l1的淺拷貝(shallow copy)還是深拷貝(deep copy)呢?

a == b是比較兩個對象的值相等,還是兩個對象完全相等呢?

1.“==”VS“is”

== 和is是python中對象比較常用的兩種方式,“==”操作符比較對象之間的值是否相等,eg

a == b

而is 操作符比較是對象的身份標識是否相等,即它們是否是同一個對象,是否指向同一個內存地址。

在python中,每個對象的身份標識,都是通過函數id(object)獲得。因此,'is'操作符,相當於比較對象之間的ID是否相等,下面的例子:

a = 10
b = 10

a == b
True

id(a)
4427562448

id(b)
4427562448

a is b
True

python會給10開闢一個內存,然後變量a和b同時指向這地位內存區域,即a和b都是指向10這個變量,因此,a和b的值相等,id也相等,a==b,和a is b都返回True.

不過要注意的是,對於整型數字來說,以上 a is b爲True的結論,只適於-5到255範圍內的數字。

a = 257
b = 257

a == b
True

id(a)
4473417552

id(b)
4473417584

a is b
False
if a is None:
      ...

if a is not None:
      ...

比較操作符'is'通常優於“==”.因爲is操作符不能重載,這樣,python就不需要去尋找,程序中是否有其他重載了比較操作符,並去調用,執行比較操作符'is'就僅是比較兩個變量的ID而已。

但是“==”操作符不同,執行a ==b 相當於去執行a.__eq__(b),而python大部分的數據類型會去重載__eq__這個函數,其內部的處理會複雜一些。對於列表,__eq__函數會去遍歷列表中的元素,比較它們的順序和值是否相等。

對於不可變的變量,如果我們之前用'=='或'is‘比較過,結果是不是就上直不變了呢?

t1 = (1, 2, [3, 4])
t2 = (1, 2, [3, 4])
t1 == t2
True

t1[-1].append(5)
t1 == t2
False

答案是否定的,我們知道元組是不可變的,但元組可以嵌套,它裏面的元素可以是列表類型,列表是可變的,所以如果我們修改了元組中的某個可變元素,那麼元組本身也就改變了,之前用'is'或是'=='操作符取得結果,可能就不適用。

2.淺拷貝和深度拷貝

常見的淺拷貝方法,是使用數據類型本身的構造器,比如:

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

這裏l2就是l1的淺拷貝,s2是s1的淺拷貝,對於可變的序列,我們還可以通過切片操作符":"完成淺拷貝,如下

l1 = [1, 2, 3]
l2 = l1[:]

l1 == l2
True

l1 is l2
False

當然,python中也提供了相對應的函數copy.copy(),適用於任何數據類型:

import copy
l1 = [1, 2, 3]
l2 = copy.copy(l1)

不過,需要注意,對於元組不講,使用tuple()或是切片操作符':'不會創建一份淺拷貝,相反,它會返回一個指向相同元組的引用

t1 = (1, 2, 3)
t2 = tuple(t1)

t1 == t2
True

t1 is t2
True

這裏,元組(1,2,3)只被創建一次,t1和t2同時指向這個元組。淺拷貝就是重新分配一塊內存,創建上個新對對象裏面的元素是原對象中子對象的引用,因此,如果原對象中的元互素不變,那無所謂,但變了,淺拷貝會的副作用,所以要注意。

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)]

首先初始化一個列表l1,裏面的元素是一個列表和一個元組,然後對l1執行淺拷貝,賦予l2.因爲淺拷貝里的元素是對原元素的引用,因此l2中的元素和l1指向同一個列表和元組對象。

2.深拷貝

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)]

無論l1如何變,l2不會變。是獨立的。

不過深拷貝也不完美,如果拷貝對象中存在指向自身的引用,那麼會陷入無限循環。

import copy
x = [1]
x.append(x)

x
[1, [...]]

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

3.總結

4.思考

跑一下代碼,檢驗自己的想法

import copy
x = [1]
x.append(x)

y = copy.deepcopy(x)

# 以下命令的輸出是?
x == y

淺拷貝,不可變的不可變,可變的依舊可變
深拷貝,都不可變

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章