[Python進階]Python Object的比較和複製

Python 的任何 variable 都是 C++ 的 Object,所以我們在對 Python 的 variable 做任何操作時,其實就是在對 Object 做。例如,我們做 variable 比較時:

1
2
if a == b:
...

variable 複製:

1
2
a = [1, 2, 3]
b = list(a)

可是如果只是”賦予”新的 variable,例如:

1
2
3
4
5
a = [1, 2, 3]
b = a
b.append(4)
a
# Output: [1, 2, 3, 4]

b 會影響 a,因為它們共享同個記憶體上的 address。不過透過上面的範例,你可能還是不太清楚:

  • a == b 是比較兩個 object address 相等呢?還是 value 相等呢?
  • b = list(a) 是 shallow copy 還是 deep copy 呢?

比較語法

在 Python 中你應該遇過下面兩個語法,你能分辨出有什麼不同嗎?

1
2
3
4
if a == b:
...
if a is b:
...

== 表示兩個 variables 的”值”是否相等,is 表示兩個 variables 是否為同一個 Object,是否 address 也相同。在 Python 中,我們可以透過 id(variable) 去拿 variable 的唯一 ID,所以判斷 a is b 如同 id(a) == id(b)。我們再看一個百思不得其解的例子:

1
2
3
4
a = 10
b = 10
a is b
# Output: True

咦?ab 是兩個不同的變數呀,為何它們的 id 會相同呢?我們說過,Python 的任何 variable 都是 C++ 的 Object,int 也不例外。然而,為了提升性能,C++ 把常用的數字 -5 到 256 先定義好,作為 cache 使用,當 Python 需要時,直接從這個 pool 拿去引用。於是乎:

1
2
3
4
a = 257
b = 257
a is b
# Output: False

有興趣的讀者可以試試。再來,我們來探討 ==is 性能的部分。通常,is 會比 == 快很多,因為 is 不會被 overload,這樣 Python 就不需要去尋找 __eq__a == b 實際上做的事情等同於 a.__eq__(b)

copy 語法

所謂的 copy,指的是重新分配一塊記憶體,創建一個新的 Object,所以它們的 id 肯定是不同的。而 copy 又分 shallow copy 和 deep copy。所謂的 shallow copy,新的 Object 裡面的元素是原本 Object 裡面元素的引用,所以如果 Object 裡面的 Object 被改了,新舊 Object 都會被連動。例如:

1
2
3
4
5
6
a = [1, [1, 2, 3]]
b = list(a)
b[1].append(4)
b[0] = 2
a
# Output: [1, [1, 2, 3, 4]]

可以看到 variable b append 4 之後,a 裡面的 list 也被影響了。而 deep copy,相對於 shallow copy 來說,會遞迴的方式往裡面一直 copy,所以新的 Object 和舊的 Object 沒有任何關聯。Python 中透過 copy.deepcopy(object) 實現 deep copy,比如:

1
2
3
4
5
6
import copy
a = [1, [1, 2, 3]]
b = copy.deepcopy(a)
b[1].append(4)
a
# Output: [1, [1, 2, 3]]

我們可以看到 a 不受任何影響即使 b append 了 4。最後我們再來探討 mutable (可變) Object 和 immutable (不可變) Object,看一下下面的例子:

1
2
3
4
5
a = 1000
b = a
a += 1
b
# Output: 1000

我們可以看到,a += 1 後竟然沒有影響 b,不是說 Python 一切 variable 皆是 object 嗎?這是因為在 Python 中 int, float, string, tuple 等屬於 immutable object,不能改 object 裡面的值。我們可以看到 a += 1 之後,a 的 id 變了,代表它被重新賦予新的 object:

1
2
3
4
5
6
a = 1000
id(a)
# Output: 140628677809328
a += 1
id(a)
# Output: 140628677809168

而 dict, array, set 等等,屬於 mutable object,在 assign 或是當作參數傳進去 function 的時候,會影響該 variable。

總結

這篇文章講了 Python 的 variable 怎麼做比較和複製:

  • Python 的一切皆為 Object,所以比較和複製的思維可以參考 C++
  • Object 間的比較分成 address 和 value 的比較,分別為 is==
  • Object 間的複製分成 shallow 和 deep copy,一個只複製了第一層的 value,另一個會遞迴複製。