new = old[:]
Python老鳥都知道以上代碼是什么意思。它復制列表old到new。它對於新手來說是種困惑而且應該避免使用這種方法。不幸的是[:]標記法被廣泛使用,可能是Python程序員不知道更好的列表復制法吧。
首先我們需要了解Python是如何管理對象和變量。Python沒有C語言中的變量。在C語言中,變量不止是個名字,它是字節集合並真實存在於內存某個位置上。而在Python中,變量僅僅是指向對象的標簽。
看看以下語句:
a = [1, 2, 3]
它表示我們創建了一個指引指向列表[1, 2, 3],但是a不是列表。如果:
b = a
我們並沒有復制a所指引的列表。我們只是創建了一個新的標簽b,然后將其指向a所指向的列表。
如果你修改a,那你就同時修改了b,因為它們指向同一個列表:
>>> a = [1, 2, 3] >>> b = a >>> a.append(4) >>> print a [1, 2, 3, 4] >>> print b [1, 2, 3, 4]
內建函數id()可以返回對象的唯一id。該id是對象的內存地址。
>>> id(a) 3086056 >>> id(b) 3086056 >>> c = [] # Create a new list >>> id(c) 2946712
可以看出a和b都指向同一個內存地址。c指向一個新建的空列表,因此指向了不同的地址。
現在我們要復制a指引的列表。我們必須創建新的列表,然后使用b指引它。
這其實就是 new = old[:]。切片運算符[:]返回一個序列的切片。切片過程是切下列表的一部分,創建新的列表,將切下的部分復制到新列表。
>>> a[1:3] [2, 3] >>> id(a) 3086056 >>> id(a[1:3]) 3063400
省略第一個索引值,切片從列表開始,省略第二個索引值,切片直到列表末端。
>>> a[:3] [1, 2, 3] >>> a[1:] [2, 3, 4]
通過調用a[:],我們得到一個從列表首端開始到末端的切片,也就是a(指引的列表)的完整復制。但這不是復制列表的唯一方式。看看下面這個情況:
>>> b = list(a) >>> id(a) 3086056 >>> id(b) 3086256
這個是不是看起來更好,少一些隱式,更加pythonic?a[:]看起來有點太像Perl。不同於切片標記法,不了解Python的人也會明白b是一個列表。
list()是列表構造函數。它會在傳入的數列基礎上新建一個列表。數列不一定是列表,它可以是任何類型的數列。
>>> my_tuple = (1, 2, 3) >>> my_list = list(my_tuple) >>> print my_list [1, 2, 3] >>> id(my_tuple) 3084496 >>> id(my_list) 3086336
而且它還接受生成器。切片筆記法不適用於生成器,因為生成器是不可更改。你不能generator[0],例如:
>>> generator = (x * 3 for x in range(4)) >>> list(generator) [0, 3, 6, 9]
百分之九十的切片標記法都可以被list()代替。下次你看見[:]的時候試試使用list()替代,這樣可以讓你的代碼更加可讀。記住,魔鬼藏在細節里。
附:五種復制方法的比較
>>> import copy
>>> a = [[10], 20]
>>> b = a[:]
>>> c = list(a)
>>> d = a * 1
>>> e = copy.copy(a)
>>> f = copy.deepcopy(a)
>>> a.append(21)
>>> a[0].append(11)
>>> print id(a), a
30553152 [[10, 11], 20, 21]
>>> print id(b), b
44969816 [[10, 11], 20]
>>> print id(c), c
44855664 [[10, 11], 20]
>>> print id(d), d
44971832 [[10, 11], 20]
>>> print id(e), e
44833088 [[10, 11], 20]
>>> print id(f), f
44834648 [[10], 20]
從以上可以看出,使用 a[:], list(a), a*1, copy.copy(a)四種方式復制列表結果都可以得到一個新的列表,但是如果列表中含有列表,所有b, c, d, e四個新列表的子列表都是指引到同一個對象上。只有使用copy.deepcopy(a)方法得到的新列表f才是包括子列表在內的完全復制。