[Python] 如何克隆或複制列表?


Answers

費利克斯已經提供了一個很好的答案,但我認為我會對各種方法進行速度比較:

  1. 10.59秒(105.9us / itn) - copy.deepcopy(old_list)
  2. 10.16秒(101.6us / itn) - 使用deepcopy複製類的純python Copy()方法
  3. 1.488秒(14.88us / itn) - 純python Copy()方法不復制類(只有字符串/列表/元組)
  4. 0.325秒(3.25us / itn) - for item in old_list: new_list.append(item)
  5. 0.217秒(2.17us / itn) - [i for i in old_list]列表理解
  6. 0.186秒(1.86us / itn) - copy.copy(old_list)
  7. 0.075秒(0.75us / itn) - list(old_list)
  8. 0.053秒(0.53us / itn) - new_list = []; new_list.extend(old_list) new_list = []; new_list.extend(old_list)
  9. 0.039秒(0.39us / itn) - old_list[:]列表切片

所以最快的是列表切片。 但要注意copy.copy()list[:]list(list) ,不像copy.deepcopy()和python版本不要復制列表中的任何列表,字典和類實例,所以如果原始文件,它們也會在復制列表中更改,反之亦然。

(如果任何人有興趣或想提出任何問題,這裡是腳本:)

from copy import deepcopy

class old_class:
    def __init__(self):
        self.blah = 'blah'

class new_class(object):
    def __init__(self):
        self.blah = 'blah'

dignore = {str: None, unicode: None, int: None, type(None): None}

def Copy(obj, use_deepcopy=True):
    t = type(obj)

    if t in (list, tuple):
        if t == tuple:
            # Convert to a list if a tuple to 
            # allow assigning to when copying
            is_tuple = True
            obj = list(obj)
        else: 
            # Otherwise just do a quick slice copy
            obj = obj[:]
            is_tuple = False

        # Copy each item recursively
        for x in xrange(len(obj)):
            if type(obj[x]) in dignore:
                continue
            obj[x] = Copy(obj[x], use_deepcopy)

        if is_tuple: 
            # Convert back into a tuple again
            obj = tuple(obj)

    elif t == dict: 
        # Use the fast shallow dict copy() method and copy any 
        # values which aren't immutable (like lists, dicts etc)
        obj = obj.copy()
        for k in obj:
            if type(obj[k]) in dignore:
                continue
            obj[k] = Copy(obj[k], use_deepcopy)

    elif t in dignore: 
        # Numeric or string/unicode? 
        # It's immutable, so ignore it!
        pass 

    elif use_deepcopy: 
        obj = deepcopy(obj)
    return obj

if __name__ == '__main__':
    import copy
    from time import time

    num_times = 100000
    L = [None, 'blah', 1, 543.4532, 
         ['foo'], ('bar',), {'blah': 'blah'},
         old_class(), new_class()]

    t = time()
    for i in xrange(num_times):
        Copy(L)
    print 'Custom Copy:', time()-t

    t = time()
    for i in xrange(num_times):
        Copy(L, use_deepcopy=False)
    print 'Custom Copy Only Copying Lists/Tuples/Dicts (no classes):', time()-t

    t = time()
    for i in xrange(num_times):
        copy.copy(L)
    print 'copy.copy:', time()-t

    t = time()
    for i in xrange(num_times):
        copy.deepcopy(L)
    print 'copy.deepcopy:', time()-t

    t = time()
    for i in xrange(num_times):
        L[:]
    print 'list slicing [:]:', time()-t

    t = time()
    for i in xrange(num_times):
        list(L)
    print 'list(L):', time()-t

    t = time()
    for i in xrange(num_times):
        [i for i in L]
    print 'list expression(L):', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        a.extend(L)
    print 'list extend:', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        for y in L:
            a.append(y)
    print 'list append:', time()-t

    t = time()
    for i in xrange(num_times):
        a = []
        a.extend(i for i in L)
    print 'generator expression extend:', time()-t

編輯 :添加新樣式,舊式的類和字符串的基準,並使python版本更快,並添加了一些更多的方法,包括列表表達式和extend()

Question

在Python中克隆或複制列表有哪些選項?

每次my_list更改時,使用new_list = my_list然後修改new_list
為什麼是這樣?




在Python中克隆或複制列表有哪些選項?

在Python 3中,可以使用以下內容進行淺度複製:

a_copy = a_list.copy()

在Python 2和Python 3中,你可以得到一個完整的原始片斷的淺拷貝:

a_copy = a_list[:]

說明

有兩種語義方法來複製列表。 淺拷貝創建相同對象的新列表,深拷貝創建包含新等價對象的新列表。

淺列表複製

淺拷貝僅複製列表本身,該列表是對列表中的對象的引用的容器。 如果包含的對象本身是可變的並且其中一個被更改,則更改將反映在這兩個列表中。

在Python 2和Python 3中有不同的方法。Python 2的方式也可以在Python 3中使用。

Python 2

在Python 2中,製作列表淺表副本的慣用方法是使用原始的完整片段:

a_copy = a_list[:]

你也可以通過列表的構造函數來完成同樣的事情,

a_copy = list(a_list)

但使用構造函數效率較低:

>>> timeit
>>> l = range(20)
>>> min(timeit.repeat(lambda: l[:]))
0.30504298210144043
>>> min(timeit.repeat(lambda: list(l)))
0.40698814392089844

Python 3

在Python 3中,列表獲取list.copy方法:

a_copy = a_list.copy()

在Python 3.5中:

>>> import timeit
>>> l = list(range(20))
>>> min(timeit.repeat(lambda: l[:]))
0.38448613602668047
>>> min(timeit.repeat(lambda: list(l)))
0.6309100328944623
>>> min(timeit.repeat(lambda: l.copy()))
0.38122922903858125

製作另一個指針不會創建副本

每次my_list更改時,使用new_list = my_list然後修改new_list。 為什麼是這樣?

my_list只是一個指向內存中實際列表的名稱。 當你說new_list = my_list你並沒有創建一個副本,你只需添加另一個名字,指向內存中的原始列表。 當我們製作副本時,我們可能會遇到類似的問題。

>>> l = [[], [], []]
>>> l_copy = l[:]
>>> l_copy
[[], [], []]
>>> l_copy[0].append('foo')
>>> l_copy
[['foo'], [], []]
>>> l
[['foo'], [], []]

該列表只是指向內容的指針數組,因此淺拷貝只是複制指針,因此您有兩個不同的列表,但它們具有相同的內容。 要製作內容的副本,您需要進行深層複製。

深拷貝

在Python 2或3中創建列表深層副本,請在copy模塊中使用deepcopy

import copy
a_deep_copy = copy.deepcopy(a_list)

為了演示如何讓我們創建新的子列表:

>>> import copy
>>> l
[['foo'], [], []]
>>> l_deep_copy = copy.deepcopy(l)
>>> l_deep_copy[0].pop()
'foo'
>>> l_deep_copy
[[], [], []]
>>> l
[['foo'], [], []]

所以我們看到深度複製列表與原始列表完全不同。 你可以推出你自己的功能 - 但不要。 通過使用標準庫的deepcopy功能,您可能會創建一些本來不會有的bug。

不要使用eval

您可能會看到這被用作深度複製的方式,但不要這樣做:

problematic_deep_copy = eval(repr(a_list))
  1. 這很危險,特別是如果你從一個你不信任的來源評估某些東西的話。
  2. 這是不可靠的,如果你正在復制的子元素沒有可以被評估以再現等同元素的表示。
  3. 它的性能也較差。

在64位Python 2.7中:

>>> import timeit
>>> import copy
>>> l = range(10)
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
27.55826997756958
>>> min(timeit.repeat(lambda: eval(repr(l))))
29.04534101486206

在64位Python 3.5上:

>>> import timeit
>>> import copy
>>> l = list(range(10))
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
16.84255409205798
>>> min(timeit.repeat(lambda: eval(repr(l))))
34.813894678023644



令我驚訝的是,這還沒有被提及,所以為了完整起見......

您可以使用“splat操作符”執行列表解壓縮操作: * ,它還會復制列表中的元素。

old_list = [1, 2, 3]

new_list = [*old_list]

new_list.append(4)
old_list == [1, 2, 3]
new_list == [1, 2, 3, 4]

這種方法明顯的缺點是它只能在Python 3.5+中使用。

雖然時序明智,但這似乎比其他常用方法更好。

x = [random.random() for _ in range(1000)]

%timeit a = list(x)
%timeit a = x.copy()
%timeit a = x[:]

%timeit a = [*x]

#: 2.47 µs ± 38.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.47 µs ± 54.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
#: 2.39 µs ± 58.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

#: 2.22 µs ± 43.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)



Python 3.6.0定時

以下是使用Python 3.6.0的計時結果。 請記住,這些時間是相互關聯的,而不是絕對的。

我堅持只做淺拷貝,並且還添加了一些Python2中不可能實現的新方法,例如list.copy() (Python3 slice等同 )和list *new_list, = list*new_list, = list ):

METHOD                  TIME TAKEN
b = a[:]                6.468942025996512   #Python2 winner
b = a.copy()            6.986593422974693   #Python3 "slice equivalent"
b = []; b.extend(a)     7.309216841997113
b = a[0:len(a)]         10.916740721993847
*b, = a                 11.046738261007704
b = list(a)             11.761539687984623
b = [i for i in a]      24.66165203397395
b = copy.copy(a)        30.853400873980718
b = []
for item in a:
  b.append(item)        48.19176080400939

考慮到Python3 list.copy()方法的可讀性越來越高,我們可以看到老贏家仍然排在前列,但並不是真的list.copy()

請注意,這些方法不會輸出除列表之外的任何輸入的等效結果。 它們都適用於可切片對象,對於任何迭代都適用,但只有copy.copy()適用於任何Python對象。

這裡是感興趣的人的測試代碼( 來自這裡的模板 ):

import timeit

COUNT = 50000000
print("Array duplicating. Tests run", COUNT, "times")
setup = 'a = [0,1,2,3,4,5,6,7,8,9]; import copy'

print("b = list(a)\t\t", timeit.timeit(stmt='b = list(a)', setup=setup, number=COUNT))
print("b = copy.copy(a)\t\t", timeit.timeit(stmt='b = copy.copy(a)', setup=setup, number=COUNT))
print("b = a.copy()\t\t", timeit.timeit(stmt='b = a.copy()', setup=setup, number=COUNT))
print("b = a[:]\t\t", timeit.timeit(stmt='b = a[:]', setup=setup, number=COUNT))
print("b = a[0:len(a)]\t", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\t", timeit.timeit(stmt='*b, = a', setup=setup, number=COUNT))
print("b = []; b.extend(a)\t", timeit.timeit(stmt='b = []; b.extend(a)', setup=setup, number=COUNT))
print("b = []\nfor item in a: b.append(item)\t", timeit.timeit(stmt='b = []\nfor item in a:  b.append(item)', setup=setup, number=COUNT))
print("b = [i for i in a]\t", timeit.timeit(stmt='b = [i for i in a]', setup=setup, number=COUNT))



Python的成語是newList = oldList[:]




讓我們從頭開始,探索一下它:

所以假設你有兩個列表:

list_1=['01','98']
list_2=[['01','98']]

我們必須複製這兩個列表,現在從第一個列表開始:

所以首先讓我們嘗試一般的複制方法:

copy=list_1

現在,如果你想複製copy_1,那麼你可能是錯的,讓我們來看看它:

The id() function shows us that both variables point to the same list object, i.e. they share this object.
print(id(copy))
print(id(list_1))

輸出:

4329485320
4329485320

驚訝嗎? 好吧,我們來探討一下:

因此,正如我們所知道的python不會將任何內容存儲在變量中,Variables只是引用對象和對象存儲的值。 這裡的對像是list但是我們通過兩個不同的變量名創建了對同一個對象的兩個引用。 所以這兩個變量都指向同一個對象:

所以當你做copy=list_1 ,它實際上做了什麼:

這裡在圖像列表1和副本是兩個變量名稱,但對像是兩個變量是相同的list

因此,如果您嘗試修改複製列表,那麼它也會修改原始列表,因為列表中只有一個列表,無論您從復制列表還是從原始列表中進行修改,都將修改該列表:

copy[0]="modify"

print(copy)
print(list_1)

輸出:

['modify', '98']
['modify', '98']

所以它修改了原來的列表:

那麼解決方案是什麼?

解答:

現在讓我們轉到復制列表的第二個pythonic方法:

copy_1=list_1[:]

現在這個方法解決了我們在第一個問題中遇到的問題讓我們來看看它:

print(id(copy_1))
print(id(list_1))

4338792136
4338791432

因此,我們可以看到我們的兩個列表都有不同的id,這意味著兩個變量都指向不同的對象,所以實際發生的事情是:

現在讓我們嘗試修改列表,看看我們是否仍然面臨上一個問題:

copy_1[0]="modify"

print(list_1)
print(copy_1)

輸出:

['01', '98']
['modify', '98']

因此,您可以看到它不會修改原始列表,它只會修改複製的列表,所以我們對它仍然滿意。

那麼現在我認為我們完成了? 等待我們必須複製第二個嵌套列表,所以讓我們嘗試pythonic方式:

copy_2=list_2[:]

所以list_2應該引用另一個對象,它是list_2的副本我們來檢查一下:

print(id((list_2)),id(copy_2))

我們得到的結果是:

4330403592 4330403528

現在我們可以假設兩個列表都指向不同的對象,所以現在讓我們試著修改它,讓我們看看它是給我們想要的:

所以當我們嘗試時:

copy_2[0][1]="modify"

print(list_2,copy_2)

它給了我們輸出:

[['01', 'modify']] [['01', 'modify']]

現在,我們使用pythonic的方式並沒有多少困惑,我們仍然面臨同樣的問題。

讓我們了解它:

所以當我們這樣做時:

copy_2=list_2[:]

我們實際上只複製外部列表,而不是嵌套列表,所以嵌套列表是兩個列表的同一個對象,讓我們來檢查:

print(id(copy_2[0]))
print(id(list_2[0]))

輸出:

4329485832
4329485832

所以實際上當我們做copy_2=list_2[:]時會發生什麼:

它創建列表的副本,但只有外部列表副本,而不是嵌套列表副本,嵌套列表對於兩個變量都是相同的,所以如果您嘗試修改嵌套列表,那麼它也會修改原始列表,因為嵌套列表對像對於兩者都是相同的嵌套列表。

那麼解決方案是什麼?

解決方案是deep copy

from copy import deepcopy
deep=deepcopy(list_2)

所以現在讓我們來看看它:

print(id((list_2)),id(deep))

輸出:

4322146056 4322148040

兩個id都不同,現在我們來檢查一下嵌套列表ID:

print(id(deep[0]))
print(id(list_2[0]))

輸出:

4322145992
4322145800

正如你可以看到這兩個ID是不同的,所以我們可以假設兩個嵌套列表現在都指向不同的對象。

所以當你做deep=deepcopy(list_2)實際上發生了什麼:

因此,兩個嵌套列表都指向不同的對象,現在他們有單獨的嵌套列表副本。

現在讓我們嘗試修改嵌套列表,看看它是否解決了上一個問題:

所以如果我們這樣做:

deep[0][1]="modify"
print(list_2,deep)

輸出:

[['01', '98']] [['01', 'modify']]

因此,您可以看到它不會修改原始嵌套列表,它只會修改複製的列表。

如果你喜歡我的詳細答案,請通過提高它來讓我知道,如果你有任何疑問了解這個答案,請評論:)




new_list = list(old_list)




一個非常簡單的方法獨立於python版本在已經給出的答案中缺少,你可以使用大部分時間(至少我是這樣做的):

new_list = my_list * 1       #Solution 1 when you are not using nested lists

但是,如果my_list包含其他容器(例如,嵌套列表),則必須按照複製庫中上述答案中的建議使用deepcopy。 例如:

import copy
new_list = copy.deepcopy(my_list)   #Solution 2 when you are using nested lists

獎勵 :如果你不想複製元素使用(又名淺拷貝):

new_list = my_list[:]

讓我們了解解決方案#1和解決方案#2之間的區別

>>> a = range(5)
>>> b = a*1
>>> a,b
([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])
>>> a[2] = 55 
>>> a,b
([0, 1, 55, 3, 4], [0, 1, 2, 3, 4])

正如你所看到的,當我們不使用嵌套列表時,解決方案#1完美工作。 讓我們來看看當我們將解決方案#1應用到嵌套列表時會發生什麼。

>>> from copy import deepcopy
>>> a = [range(i,i+4) for i in range(3)]
>>> a
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> b = a*1
>>> c = deepcopy(a)
>>> for i in (a, b, c): print i   
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> a[2].append('99')
>>> for i in (a, b, c): print i   
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5, 99]]   #Solution#1 didn't work in nested list
[[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]]       #Solution #2 - DeepCopy worked in nested list