可變與不可變

Python 中有兩種型別。不可變型別和可變型別。

Immutables

無法更改不可變型別的物件。任何修改物件的嘗試都將導致建立副本。

此類別包括:整數,浮點數,複數,字串,位元組,元組,範圍和 frozensets。

為了突出這個屬性,讓我們玩 id 內建。此函式返回作為引數傳遞的物件的唯一識別符號。如果 id 相同,則這是相同的物件。如果它改變了,那麼這是另一個物件。 (有人說這實際上是物件的記憶體地址,但要小心它們,它們來自力量的黑暗面……)

>>> a = 1
>>> id(a)
140128142243264
>>> a += 2
>>> a
3
>>> id(a)
140128142243328

好吧,1 不是 3 ……突發新聞……也許不是。但是,當涉及到更復雜的型別(尤其是字串)時,通常會忘記這種行為。

>>> stack = "Overflow"
>>> stack
'Overflow'
>>> id(stack)
140128123955504
>>> stack += " rocks!"
>>> stack
'Overflow rocks!'

啊哈! 看到?我們可以修改它!

>>> id(stack)
140128123911472

不。雖然看起來我們可以更改變數 stack 命名的字串,但我們實際上做的是建立一個新物件來包含連線的結果。我們被愚弄了,因為在這個過程中,舊的物體無處可去,所以它被摧毀了。在另一種情況下,這將更加明顯:

>>> stack = "Stack"
>>> stackoverflow = stack + "Overflow"
>>> id(stack)
140128069348184
>>> id(stackoverflow)
140128123911480

在這種情況下,很明顯,如果我們想要保留第一個字串,我們需要一個副本。但是對於其他型別來說這是如此明顯嗎?

行使

現在,知道不可變型別如何工作,你會用下面的程式碼說什麼?這是明智的嗎?

s = ""
for i in range(1, 1000):
    s += str(i)
    s += ","

Mutables

可以改變可變型別的物件,並且它可以原位改變。沒有隱式副本。

此類別包括:列表,詞典,位元組陣列和集合。

讓我們繼續玩我們的小 id 功能。

>>> b = bytearray(b'Stack')
>>> b
bytearray(b'Stack')
>>> b = bytearray(b'Stack')
>>> id(b)
140128030688288
>>> b += b'Overflow'
>>> b
bytearray(b'StackOverflow')
>>> id(b)
140128030688288

(作為旁註,我使用包含 ascii 資料的位元組來明確我的觀點,但請記住,位元組不是為了儲存文字資料而設計的。請原諒我。)

我們有什麼?我們建立一個 bytearray,修改它並使用 id,我們可以確保這是同一個物件,經過修改。不是它的副本。

當然,如果要經常修改物件,則可變型別比不可變型別做得好得多。不幸的是,這種屬性的現實經常被人們所遺忘。

>>> c = b
>>> c += b' rocks!'
>>> c
bytearray(b'StackOverflow rocks!')

好的…

>>> b
bytearray(b'StackOverflow rocks!')

Waiiit 第二……

>>> id(c) == id(b)
True

確實。c 不是 b 的副本。cb

行使

現在你更好地理解一個可變型別隱含的副作用,你能解釋一下這個例子中出了什麼問題嗎?

>>> ll = [ [] ]*4 # Create a list of 4 lists to contain our results
>>> ll
[[], [], [], []]
>>> ll[0].append(23) # Add result 23 to first list
>>> ll
[[23], [23], [23], [23]]
>>> # Oops...