機
器
人
Python中的列表複製
在學習完列表(list)的基本使用後,這裡有一個相當重要的重點要獨立出來討論。這個章節中,我們將會關注於列表的複製上:這個重點看似簡單,但是在實際使用時,我們經常會忘了它。
「=」的列表宣告
假設現在我們有一個列表 a,帶有許多的數字(已知條件)。今天我們被要求要做出一個列表 b,帶有與列表 a 相同的值,那我們會直覺地這樣寫:
>>> a = [1, 3, 5, 7, 9]
>>> b = a
這麼一來,我們就可以印出列表 a 和 b,看看兩個列表的值是否相等:
>>> print(a)
[1, 3, 5, 7, 9]
>>> print(b)
[1, 3, 5, 7, 9]
的確,兩個列表所帶有的值是相同的,這符合我們的預期。現在,讓我們把列表 b 稍稍地做修正,將 b[2]
的位置變成數字 100,並且印出 b:
>>> b[2] = 100
>>> print(b)
[1, 3, 100, 7, 9]
好的,目前為止事情都符合我們的預期,但是,如果現在將列表 a 印出來呢?我們將會看到什麼?讓我們試試看:
>>> print(a)
[1, 3, 100, 7, 9]
咦?為什麼 a[2]
也跟著變成 100 了?或許你會覺得,為什麼這樣很奇怪?讓我們看看這件事情背後的意義:我們將變數 b 的值宣告為變數 a 的值,然而在我們改變變數 b 後,變數 a 卻跟著改變了!
我們可以比較一下其它不是列表的資料型態,看看它們運作起來與列表有什麼樣的不同:
>>> c = 3
>>> d = c
>>> d *= 5
>>> print(c)
3
>>> print(d)
15
當變數是數字時,我們改變變數 d 的值,並沒有改變變數 c 的值。同樣的狀況也會在字串、布林值等資料型態上發生。那麼,我們要如何解釋在列表上發生的神奇現象呢?
id() 與記憶體的儲存
原來,列表這種資料型態,在電腦記憶體中的運作模式與其它變數有些許的不同。當我們對數字的變數做下列的動作:
>>> a = 2
>>> b = a
在第一行程式中,電腦在記憶體中建立了一個數字 2,並且將這個數字賦予給變數 a。在第二行程式中,電腦將剛才建立的那個數字 2 也賦予給變數 b。所以目前為止,a 和 b 兩個變數所代表的值,在記憶體中是相同的數字 2。
>>> b += 3
>>> print(b)
5
>>> print(a)
2
然而,當我們像上面這樣改變變數 b 的值時,電腦就會在記憶體中建立一個全新的數字,並將其賦予給變數 b。舉例來說,剛才變數 b 和變數 a 都代表著記憶體中相同的一個數字 2,但是當我們執行 b += 3
時,電腦就會把變數 b 指向一個記憶體中全新的數字—數字 5。
上述這種狀況,在列表的運作中是不會發生的。我們用相同的動作對兩個全新的 a 和 b 變數進行操作,看看會發生什麼事:
>>> a = [1, 3, 5]
>>> b = a
在第一行程式中,電腦在記憶體中建立了一個列表,並且建立了 1、3、5 三個數字儲存在列表中,最後將這個列表賦予給變數 a。接著到了第二行,電腦看到了 b = a
後,便將剛才的那個列表也賦予給了變數 b。接下來,讓我們試著改變列表 b 中的項目:
>>> b[1] = 7
>>> print(b)
[1, 7, 5]
>>> print(a)
[1, 7, 5]
在第一行中,電腦將列表 b 中的 1 號位置(第 2 個位置)改變為 7。此時,不同於數字的是,電腦在列表 b 被改變時,並沒有去改變它在記憶體上的位置。因此,變數 b 跟一開始一樣,與變數 a 指向同一個記憶體的位置。於是我們發現,變數 a 的值也跟著改變了。
要確認電腦有沒有改變的值在記憶體中儲存的位置,在 Python 中是相當簡單的。我們可以使用 Python 的一個內建函數 id()
來知道某比資料在記憶體中的確切位置。讓我們看看剛才數字的例子:
>>> a = 3
>>> id(a)
140720375994080
>>> b = a
>>> id(a)
140720375994080
在我的電腦上執行時,變數 a 所帶有的數字 3 是儲存在記憶體中編號 140720375994080 的位置的。這裡可以看到,在我們把變數 b 的值設為 a 後,b 變數也一起指向了記憶體中的編號 140720375994080 位置。然後,讓我們改變變數 b:
>>> b += 5
>>> id(b)
140720375994240
>>> id(a)
140720375994080
我們可以看到變數 b 所指向的記憶體位置改變了,而變數 a 還是保持一樣的,因此我們可以知道這時候 a 與 b 是兩個互相獨立、互不關聯的變數。同樣的 id()
函數也可以被運用在列表變數上。讓我們跟剛剛一樣宣告兩個列表:
>>> a = [1, 3, 5]
>>> id(a)
1296034383552
>>> b = a
>>> id(b)
1296034383552
可以看到,變數 a 和變數所代表的列表是同一個列表,而這件事情會一直為真,直到我們將變數 a 或 b 重新定義一個值。
>>> b[1] = 7
>>> id(b)
1296034383552
>>> id(a)
1296034383552
>>> print(a)
[1, 7, 5]
>>> print(b)
[1, 7, 5]
這麼一來我們便知道,當我們要建立一個變數,其值與一個已經存在的列表相同時,不能直接使用等號 =
來做變數宣告。不果,如果不能直接使用等號 =
,那我們該用什麼呢?
copy() 函數
幸好,在 Python 這個體貼的程式語言中,早就有建立專門執行這項動作的方法。這個方法,屬於列表這個資料型態,叫做 copy()
。在使用時,我們可以像下面這樣:
>>> a = [1, 3, 5]
>>> b = a.copy()
就這樣短短的一兩行程式,我們就成功地將列表 a 複製了一次,代表著電腦在記憶體中已經有了兩個 [1, 3, 5]
的列表,而不是像剛才那樣只有一個。因此,如果我們改變列表 b 的值:
>>> b[1] = 7
>>> print(b)
[1, 7, 5]
>>> print(a)
[1, 3, 5]
這麼一來,我們就可以成功地複製一個列表,並且不需要擔心列表會不會受到另一個列表的影響。然而,有時這個列表的內容物不一定只是數字—或許列表中還有其它的列表?面對這種多維列表,我們複製的方式又不太一樣了。這點,讓我們在之後的章節討論。