Làm thế nào để sao chép hoặc sao chép một danh sách?


2549

Các tùy chọn để sao chép hoặc sao chép danh sách trong Python là gì?

Trong khi sử dụng new_list = my_list, bất kỳ sửa đổi để new_listthay đổi my_listmọi lúc. Tại sao lại thế này?

Câu trả lời:


3330

Với new_list = my_list, bạn thực sự không có hai danh sách. Bài tập chỉ sao chép tham chiếu vào danh sách, không phải danh sách thực tế, vì vậy cả hai new_listmy_listtham chiếu đến cùng một danh sách sau khi gán.

Để thực sự sao chép danh sách, bạn có nhiều khả năng khác nhau:

  • Bạn có thể sử dụng list.copy()phương thức dựng sẵn (có sẵn từ Python 3.3):

    new_list = old_list.copy()
  • Bạn có thể cắt nó:

    new_list = old_list[:]

    Ý kiến của Alex Martelli (ít nhất là vào năm 2007 ) về điều này là, đó là một cú pháp kỳ lạ và không có ý nghĩa gì khi sử dụng nó . ;) (Theo ý kiến ​​của anh ấy, cái tiếp theo dễ đọc hơn).

  • Bạn có thể sử dụng list()chức năng tích hợp:

    new_list = list(old_list)
  • Bạn có thể sử dụng chung chung copy.copy():

    import copy
    new_list = copy.copy(old_list)
    

    Điều này chậm hơn một chút so với list()vì nó phải tìm ra kiểu dữ liệu old_listđầu tiên.

  • Nếu danh sách chứa các đối tượng và bạn cũng muốn sao chép chúng, hãy sử dụng chung copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)
    

    Rõ ràng là phương pháp chậm nhất và cần nhiều bộ nhớ nhất, nhưng đôi khi không thể tránh khỏi.

Thí dụ:

import copy

class Foo(object):
    def __init__(self, val):
         self.val = val

    def __repr__(self):
        return 'Foo({!r})'.format(self.val)

foo = Foo(1)

a = ['foo', foo]
b = a.copy()
c = a[:]
d = list(a)
e = copy.copy(a)
f = copy.deepcopy(a)

# edit orignal list and instance 
a.append('baz')
foo.val = 5

print('original: %r\nlist.copy(): %r\nslice: %r\nlist(): %r\ncopy: %r\ndeepcopy: %r'
      % (a, b, c, d, e, f))

Kết quả:

original: ['foo', Foo(5), 'baz']
list.copy(): ['foo', Foo(5)]
slice: ['foo', Foo(5)]
list(): ['foo', Foo(5)]
copy: ['foo', Foo(5)]
deepcopy: ['foo', Foo(1)]

7
Nếu tôi không nhầm: newlist = [*mylist]cũng là một khả năng trong Python 3. newlist = list(mylist)có lẽ rõ ràng hơn.
Stéphane

9
khả năng khác là new_list = old_list * 1
aris

4
Phương pháp nào trong số này là bản sao nông và phương pháp nào là bản sao sâu?
Eswar

4
@Eswar: tất cả trừ người cuối cùng làm một bản sao nông
Felix Kling

3
@Eswar nó là một bản sao nông.
juanpa.arrivillaga

604

Felix đã cung cấp một câu trả lời tuyệt vời, nhưng tôi nghĩ tôi sẽ so sánh tốc độ của các phương pháp khác nhau:

  1. 10,59 giây (105,9us / itn) - copy.deepcopy(old_list)
  2. 10,16 giây (101,6us / itn) - Copy()các lớp sao chép phương thức python thuần túy với độ sâu
  3. 1,488 giây (14,88us / itn) - Copy()phương thức python thuần không sao chép các lớp (chỉ có dicts / list / tuples)
  4. 0,325 giây (3,25us / itn) - for item in old_list: new_list.append(item)
  5. 0,217 giây (2,17us / itn) - [i for i in old_list]( hiểu danh sách )
  6. 0,186 giây (1,86us / itn) - copy.copy(old_list)
  7. 0,075 giây (0,75us / itn) - list(old_list)
  8. 0,053 giây (0,53us / itn) - new_list = []; new_list.extend(old_list)
  9. 0,039 giây (0,39us / itn) - old_list[:]( cắt danh sách )

Vì vậy, nhanh nhất là danh sách cắt. Nhưng hãy lưu ý rằng copy.copy(), list[:]list(list), không giống như copy.deepcopy()và phiên bản python không sao chép bất kỳ danh sách, từ điển và thể hiện lớp nào trong danh sách, vì vậy nếu bản gốc thay đổi, chúng cũng sẽ thay đổi trong danh sách được sao chép và ngược lại.

(Đây là kịch bản nếu bất kỳ ai quan tâm hoặc muốn nêu ra bất kỳ vấn đề nào :)

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

9
Vì bạn đang đo điểm chuẩn, nên có thể bao gồm một điểm tham chiếu. Những số liệu này có còn chính xác trong năm 2017 khi sử dụng Python 3.6 với mã được biên dịch đầy đủ không? Tôi đang lưu ý câu trả lời dưới đây ( stackoverflow.com/a/17810305/26219 ) đã đặt câu hỏi cho câu trả lời này.
Đánh dấu Edington

4
sử dụng timeitmô-đun. Ngoài ra, bạn không thể kết luận nhiều từ các điểm chuẩn vi mô tùy ý như thế này.
Corey Goldberg

3
Nếu bạn muốn bao gồm một tùy chọn mới cho 3,5+, [*old_list]tương đương với list(old_list), nhưng vì đó là cú pháp, không phải là đường dẫn hàm gọi chung, nên sẽ tiết kiệm được một chút khi chạy (và không giống như old_list[:], không gõ chuyển đổi, [*old_list]hoạt động trên bất kỳ lần lặp nào và tạo ra a list).
ShadowRanger

3
@CoreyGoldberg cho điểm chuẩn vi mô ít tùy ý hơn một chút (sử dụng timeit, chạy 50m thay vì 100k) xem stackoverflow.com/a/43220129/3745896
Sông

1
@ShadowRanger [*old_list]thực sự có vẻ tốt hơn hầu hết các phương pháp khác. (xem câu trả lời của tôi được liên kết trong các bình luận trước)
Sông

151

Tôi đã được thông báo rằng Python 3.3+ thêmlist.copy() phương thức, sẽ nhanh như cắt:

newlist = old_list.copy()


6
Có, và theo tài liệu docs.python.org/3/l Library / stdtypes.html#mutable-resultence-type , s.copy()tạo một bản sao nông s(giống như s[:]).
CyberMew

Trên thực tế có vẻ như hiện nay, python3.8, .copy()nhanh hơn một chút so với cắt. Xem bên dưới @AaronsHall trả lời.
yêu.by.Jesus

126

Các tùy chọn để sao chép hoặc sao chép danh sách trong Python là gì?

Trong Python 3, một bản sao nông có thể được tạo bằng:

a_copy = a_list.copy()

Trong Python 2 và 3, bạn có thể nhận được một bản sao nông với một lát đầy đủ của bản gốc:

a_copy = a_list[:]

Giải trình

Có hai cách ngữ nghĩa để sao chép một danh sách. Một bản sao nông tạo ra một danh sách mới của cùng một đối tượng, một bản sao sâu tạo ra một danh sách mới chứa các đối tượng tương đương mới.

Bản sao danh sách nông

Một bản sao nông chỉ sao chép chính danh sách, là nơi chứa các tham chiếu đến các đối tượng trong danh sách. Nếu các đối tượng chứa chính chúng là có thể thay đổi và một đối tượng được thay đổi, thay đổi sẽ được phản ánh trong cả hai danh sách.

Có nhiều cách khác nhau để làm điều này trong Python 2 và 3. Các cách Python 2 cũng sẽ hoạt động trong Python 3.

Con trăn 2

Trong Python 2, cách thành ngữ để tạo một bản sao nông của danh sách là với một lát hoàn chỉnh của bản gốc:

a_copy = a_list[:]

Bạn cũng có thể thực hiện điều tương tự bằng cách chuyển danh sách qua hàm tạo danh sách,

a_copy = list(a_list)

nhưng sử dụng hàm tạo thì kém hiệu quả hơn:

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

Con trăn 3

Trong Python 3, danh sách có được list.copyphương thức:

a_copy = a_list.copy()

Trong 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

Tạo một con trỏ khác không tạo ra một bản sao

Sử dụng new_list = my_list sau đó sửa đổi new_list mỗi khi my_list thay đổi. Tại sao lại thế này?

my_listchỉ là một tên chỉ đến danh sách thực tế trong bộ nhớ. Khi bạn nói rằng new_list = my_listbạn không tạo một bản sao, bạn chỉ cần thêm một tên khác trỏ vào danh sách gốc đó trong bộ nhớ. Chúng tôi có thể có các vấn đề tương tự khi chúng tôi tạo bản sao của danh sách.

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

Danh sách này chỉ là một mảng các con trỏ tới nội dung, vì vậy một bản sao nông chỉ sao chép các con trỏ và do đó bạn có hai danh sách khác nhau, nhưng chúng có cùng nội dung. Để tạo bản sao của nội dung, bạn cần một bản sao sâu.

Bản sao sâu

Để tạo một bản sao sâu của danh sách, trong Python 2 hoặc 3, hãy sử dụng deepcopytrong copymô-đun :

import copy
a_deep_copy = copy.deepcopy(a_list)

Để giải thích cách điều này cho phép chúng tôi tạo danh sách phụ mới:

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

Và vì vậy chúng tôi thấy rằng danh sách được sao chép sâu là một danh sách hoàn toàn khác với bản gốc. Bạn có thể cuộn chức năng của riêng bạn - nhưng không. Bạn có thể tạo ra các lỗi mà bạn không có bằng cách sử dụng chức năng phân tích sâu của thư viện chuẩn.

Đừng dùng eval

Bạn có thể thấy điều này được sử dụng như một cách để chiếu sâu, nhưng đừng làm điều đó:

problematic_deep_copy = eval(repr(a_list))
  1. Điều đó thật nguy hiểm, đặc biệt nếu bạn đang đánh giá thứ gì đó từ một nguồn mà bạn không tin tưởng.
  2. Điều đó không đáng tin cậy, nếu một phân khúc bạn đang sao chép không có đại diện có thể được đánh giá để tái tạo một yếu tố tương đương.
  3. Nó cũng ít hiệu suất hơn.

Trong Python 64 bit 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

trên 64 bit 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

1
Bạn không cần một bản sao sâu nếu danh sách là 2D. Nếu đó là danh sách các danh sách và những danh sách đó không có danh sách bên trong chúng, bạn có thể sử dụng vòng lặp for. Hiện tại, tôi đang sử dụng list_copy=[] for item in list: list_copy.append(copy(item))và nó nhanh hơn nhiều.
John Locke

54

Đã có nhiều câu trả lời cho bạn biết cách tạo một bản sao phù hợp, nhưng không ai trong số họ nói lý do tại sao 'bản sao' ban đầu của bạn thất bại.

Python không lưu trữ giá trị trong các biến; nó liên kết tên với các đối tượng. Nhiệm vụ ban đầu của bạn đã lấy đối tượng được đề cập đến my_listvà ràng buộc nó new_listlà tốt. Cho dù bạn sử dụng tên nào thì vẫn chỉ có một danh sách, vì vậy những thay đổi được thực hiện khi đề cập đến nó my_listsẽ vẫn tồn tại khi đề cập đến nó new_list. Mỗi câu trả lời khác cho câu hỏi này cung cấp cho bạn những cách khác nhau để tạo một đối tượng mới để liên kết new_list.

Mỗi phần tử của một danh sách hoạt động như một tên, trong đó mỗi phần tử liên kết không độc quyền với một đối tượng. Một bản sao nông tạo ra một danh sách mới có các phần tử liên kết với các đối tượng như trước.

new_list = list(my_list)  # or my_list[:], but I prefer this syntax
# is simply a shorter way of:
new_list = [element for element in my_list]

Để đưa danh sách của bạn sao chép thêm một bước, hãy sao chép từng đối tượng mà danh sách của bạn đề cập đến và liên kết các bản sao phần tử đó vào một danh sách mới.

import copy  
# each element must have __copy__ defined for this...
new_list = [copy.copy(element) for element in my_list]

Đây chưa phải là một bản sao sâu, bởi vì mỗi thành phần của danh sách có thể đề cập đến các đối tượng khác, giống như danh sách bị ràng buộc với các yếu tố của nó. Để sao chép đệ quy mọi phần tử trong danh sách, và sau đó từng đối tượng khác được gọi bởi mỗi phần tử, v.v .: thực hiện một bản sao sâu.

import copy
# each element must have __deepcopy__ defined for this...
new_list = copy.deepcopy(my_list)

Xem tài liệu để biết thêm thông tin về các trường hợp góc trong sao chép.


38

Sử dụng thing[:]

>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>> 

35

Hãy bắt đầu lại từ đầu và khám phá câu hỏi này.

Vì vậy, giả sử bạn có hai danh sách:

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

Và chúng tôi phải sao chép cả hai danh sách, bây giờ bắt đầu từ danh sách đầu tiên:

Vì vậy, trước tiên hãy thử bằng cách đặt biến copyvào danh sách ban đầu của chúng tôi , list_1:

copy=list_1

Bây giờ nếu bạn đang nghĩ sao chép danh sách_1, thì bạn đã nhầm. Các idchức năng có thể cho chúng ta thấy nếu hai biến thể trỏ đến cùng một đối tượng. Chúng ta hãy cố gắng này:

print(id(copy))
print(id(list_1))

Đầu ra là:

4329485320
4329485320

Cả hai biến là cùng một đối số chính xác. Bạn có ngạc nhiên không

Vì vậy, như chúng ta đã biết python không lưu trữ bất cứ thứ gì trong một biến, Biến chỉ tham chiếu đến đối tượng và đối tượng lưu trữ giá trị. Ở đây đối tượng là một listnhưng chúng ta đã tạo hai tham chiếu đến cùng một đối tượng bằng hai tên biến khác nhau. Điều này có nghĩa là cả hai biến đều trỏ đến cùng một đối tượng, chỉ với các tên khác nhau.

Khi bạn làm copy=list_1, nó thực sự đang làm:

nhập mô tả hình ảnh ở đây

Ở đây trong danh sách hình ảnh_1 và bản sao là hai tên biến nhưng đối tượng giống nhau cho cả hai biến đó là list

Vì vậy, nếu bạn cố gắng sửa đổi danh sách đã sao chép thì nó cũng sẽ sửa đổi danh sách gốc vì danh sách chỉ có một danh sách ở đó, bạn sẽ sửa đổi danh sách đó bất kể bạn làm gì từ danh sách đã sao chép hoặc từ danh sách gốc:

copy[0]="modify"

print(copy)
print(list_1)

đầu ra:

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

Vì vậy, nó đã sửa đổi danh sách ban đầu:

Bây giờ chúng ta hãy chuyển sang một phương pháp pythonic để sao chép danh sách.

copy_1=list_1[:]

Phương pháp này khắc phục sự cố đầu tiên chúng tôi có:

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

4338792136
4338791432

Vì vậy, như chúng ta có thể thấy cả hai danh sách của chúng ta có id khác nhau và điều đó có nghĩa là cả hai biến đều trỏ đến các đối tượng khác nhau. Vì vậy, những gì thực sự xảy ra ở đây là:

nhập mô tả hình ảnh ở đây

Bây giờ, hãy thử sửa đổi danh sách và xem liệu chúng ta có còn phải đối mặt với vấn đề trước đó không:

copy_1[0]="modify"

print(list_1)
print(copy_1)

Đầu ra là:

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

Như bạn có thể thấy, nó chỉ sửa đổi danh sách được sao chép. Điều đó có nghĩa là nó đã làm việc.

Bạn có nghĩ rằng chúng ta đã hoàn thành? Không. Hãy thử sao chép danh sách lồng nhau của chúng tôi.

copy_2=list_2[:]

list_2nên tham chiếu đến một đối tượng khác là bản sao của list_2. Hãy kiểm tra:

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

Chúng tôi nhận được đầu ra:

4330403592 4330403528

Bây giờ chúng ta có thể giả sử cả hai danh sách đang trỏ đối tượng khác nhau, vì vậy bây giờ chúng ta hãy thử sửa đổi nó và hãy xem nó đang đưa ra những gì chúng ta muốn:

copy_2[0][1]="modify"

print(list_2,copy_2)

Điều này cho chúng ta đầu ra:

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

Điều này có vẻ hơi khó hiểu, bởi vì cùng một phương pháp chúng ta đã sử dụng trước đây đã làm việc. Hãy cố gắng hiểu điều này.

Khi bạn làm:

copy_2=list_2[:]

Bạn chỉ sao chép danh sách bên ngoài, không phải danh sách bên trong. Chúng ta có thể sử dụng idchức năng một lần nữa để kiểm tra điều này.

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

Đầu ra là:

4329485832
4329485832

Khi chúng tôi làm copy_2=list_2[:], điều này xảy ra:

nhập mô tả hình ảnh ở đây

Nó tạo bản sao của danh sách nhưng chỉ sao chép danh sách bên ngoài, không phải bản sao danh sách lồng nhau, danh sách lồng nhau giống nhau cho cả hai biến, vì vậy nếu bạn cố gắng sửa đổi danh sách lồng nhau thì nó sẽ sửa đổi danh sách ban đầu vì đối tượng danh sách lồng nhau giống nhau cho cả hai danh sách.

Giải pháp là gì? Giải pháp là deepcopychức năng.

from copy import deepcopy
deep=deepcopy(list_2)

Hãy kiểm tra điều này:

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

4322146056 4322148040

Cả hai danh sách bên ngoài có ID khác nhau, chúng ta hãy thử điều này trên các danh sách lồng nhau bên trong.

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

Đầu ra là:

4322145992
4322145800

Như bạn có thể thấy cả hai ID đều khác nhau, có nghĩa là chúng ta có thể giả sử rằng cả hai danh sách lồng nhau đang trỏ đối tượng khác nhau.

Điều này có nghĩa là khi bạn làm deep=deepcopy(list_2)những gì thực sự xảy ra:

nhập mô tả hình ảnh ở đây

Cả hai danh sách lồng nhau đều trỏ đối tượng khác nhau và chúng có bản sao riêng của danh sách lồng nhau.

Bây giờ, hãy thử sửa đổi danh sách lồng nhau và xem liệu nó có giải quyết được vấn đề trước đó hay không:

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

Nó xuất ra:

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

Như bạn có thể thấy, nó không sửa đổi danh sách lồng nhau ban đầu, nó chỉ sửa đổi danh sách được sao chép.


34

Thành ngữ của Python để làm điều này là newList = oldList[:]


34

Thời gian Python 3.6

Dưới đây là kết quả thời gian sử dụng Python 3.6.8. Hãy ghi nhớ những thời điểm này là tương đối với nhau, không tuyệt đối.

Tôi bị mắc kẹt khi chỉ thực hiện các bản sao nông và cũng đã thêm một số phương thức mới không thể có trong Python2, chẳng hạn như list.copy()( tương đương lát cắt Python3 ) và hai dạng giải nén danh sách ( *new_list, = listnew_list = [*list]):

METHOD                  TIME TAKEN
b = [*a]                2.75180600000021
b = a * 1               3.50215399999990
b = a[:]                3.78278899999986  # Python2 winner (see above)
b = a.copy()            4.20556500000020  # Python3 "slice equivalent" (see above)
b = []; b.extend(a)     4.68069800000012
b = a[0:len(a)]         6.84498999999959
*b, = a                 7.54031799999984
b = list(a)             7.75815899999997
b = [i for i in a]      18.4886440000000
b = copy.copy(a)        18.8254879999999
b = []
for item in a:
  b.append(item)        35.4729199999997

Chúng ta có thể thấy người chiến thắng Python2 vẫn làm tốt, nhưng không vượt qua Python3 list.copy()nhiều, đặc biệt là xem xét khả năng đọc vượt trội của cái sau.

Con ngựa đen là phương pháp giải nén và đóng gói lại ( b = [*a]), nhanh hơn ~ 25% so với cắt thô và nhanh hơn gấp đôi so với phương pháp giải nén khác ( *b, = a).

b = a * 1 cũng làm tốt đáng ngạc nhiên.

Lưu ý rằng các phương thức này không xuất kết quả tương đương cho bất kỳ đầu vào nào ngoài danh sách. Tất cả chúng đều hoạt động cho các đối tượng có thể cắt được, một số ít hoạt động cho bất kỳ lần lặp nào, nhưng chỉ copy.copy()hoạt động cho các đối tượng Python tổng quát hơn.


Đây là mã thử nghiệm cho các bên quan tâm ( Mẫu từ đây ):

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", 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\t", timeit.timeit(stmt='b = a[0:len(a)]', setup=setup, number=COUNT))
print("*b, = a\t\t\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 = []; for 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))
print("b = [*a]\t\t", timeit.timeit(stmt='b = [*a]', setup=setup, number=COUNT))
print("b = a * 1\t\t", timeit.timeit(stmt='b = a * 1', setup=setup, number=COUNT))

1
Có thể xác nhận vẫn là một câu chuyện tương tự trên 3.8 b=[*a]- một cách rõ ràng để làm điều đó;).
SuperShoot

20

Tất cả những người đóng góp khác đã đưa ra câu trả lời tuyệt vời , hoạt động khi bạn có một danh sách thứ nguyên (được phân cấp), tuy nhiên các phương pháp được đề cập cho đến nay, chỉ copy.deepcopy()hoạt động để sao chép / sao chép danh sách và không trỏ đến các listđối tượng lồng nhau khi bạn làm việc với các danh sách đa chiều, lồng nhau (danh sách các danh sách). Trong khi Felix Kling đề cập đến nó trong câu trả lời của mình, có một chút nữa về vấn đề này và có thể là một cách giải quyết bằng cách sử dụng các công cụ tích hợp có thể chứng minh sự thay thế nhanh hơn deepcopy.

Trong khi new_list = old_list[:], copy.copy(old_list)'và đối với Py3k old_list.copy()hoạt động cho các danh sách một cấp, chúng quay trở lại chỉ vào các listđối tượng được lồng trong old_listnew_listvà thay đổi thành một trong các listđối tượng được nối tiếp với nhau.

Chỉnh sửa: Thông tin mới được đưa ra ánh sáng

Như được chỉ ra bởi cả Aaron HallPM 2Ring, việc sử dụng eval()không chỉ là một ý tưởng tồi, mà còn chậm hơn nhiều copy.deepcopy().

Điều này có nghĩa là đối với các danh sách đa chiều, lựa chọn duy nhất là copy.deepcopy(). Như đã nói, nó thực sự không phải là một lựa chọn vì hiệu suất sẽ đi về phía nam khi bạn cố gắng sử dụng nó trên một mảng đa chiều có kích thước vừa phải. Tôi đã cố gắng timeitsử dụng một mảng 42x42, không phải là chưa từng thấy hoặc thậm chí là lớn cho các ứng dụng tin sinh học, và tôi đã từ bỏ việc chờ phản hồi và chỉ bắt đầu gõ bản chỉnh sửa của mình cho bài đăng này.

Có vẻ như lựa chọn thực sự duy nhất sau đó là khởi tạo nhiều danh sách và hoạt động trên chúng một cách độc lập. Nếu bất cứ ai có bất kỳ đề xuất nào khác, về cách xử lý sao chép danh sách đa chiều, nó sẽ được đánh giá cao.

Như những người khác đã nêu, có các vấn đề hiệu suất đáng kể khi sử dụng copymô-đun và copy.deepcopy cho các danh sách đa chiều .


5
Điều này sẽ không luôn hoạt động, vì không có gì đảm bảo rằng chuỗi được trả về repr()là đủ để tạo lại đối tượng. Ngoài ra, eval()là một công cụ của phương sách cuối cùng; thấy Eval thực sự nguy hiểm bởi Ned Batchelder kỳ cựu của SO để biết chi tiết. Vì vậy, khi bạn ủng hộ việc sử dụng, eval()bạn thực sự nên đề cập rằng nó có thể nguy hiểm.
PM 2Ring 10/07/2015

1
Điểm công bằng. Mặc dù tôi nghĩ rằng quan điểm của Batchelder là nói chung việc có eval()chức năng trong Python là một rủi ro. Nó không quá nhiều cho dù bạn có sử dụng hàm trong mã hay không nhưng đó là một lỗ hổng bảo mật trong Python. Ví dụ của tôi không sử dụng nó với một chức năng tiếp nhận đầu vào từ input(), sys.agrvhoặc thậm chí là một tập tin văn bản. Nó giống với các dòng khởi tạo một danh sách đa chiều trống một lần, và sau đó chỉ có cách sao chép nó trong một vòng lặp thay vì khởi tạo lại ở mỗi lần lặp của vòng lặp.
AMR

1
Như @AaronHall đã chỉ ra, có thể có một vấn đề hiệu suất đáng kể khi sử dụng new_list = eval(repr(old_list)), vì vậy bên cạnh đó là một ý tưởng tồi, có lẽ nó cũng hoạt động quá chậm.
AMR

13

Điều làm tôi ngạc nhiên là điều này chưa được đề cập đến, vì vậy để hoàn thiện ...

Bạn có thể thực hiện giải nén danh sách với "toán tử splat" : *, cũng sẽ sao chép các phần tử của danh sách của bạn.

old_list = [1, 2, 3]

new_list = [*old_list]

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

Nhược điểm rõ ràng của phương pháp này là nó chỉ có sẵn trong Python 3.5+.

Mặc dù thời gian khôn ngoan, điều này dường như thực hiện tốt hơn so với các phương pháp phổ biến khác.

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)

1
Phương pháp này ứng xử như thế nào khi sửa đổi các bản sao?
not2qubit

2
@ not2qubit có nghĩa là bạn nối thêm hoặc chỉnh sửa các thành phần của danh sách mới. Trong ví dụ old_listnew_listlà hai danh sách khác nhau, việc chỉnh sửa một danh sách sẽ không thay đổi danh sách khác (trừ khi bạn trực tiếp tự thay đổi các thành phần (như danh sách danh sách), không có phương pháp nào trong số này là bản sao sâu).
SCB

8

Một cách tiếp cận rất đơn giản, độc lập với phiên bản python đã bị thiếu trong các câu trả lời đã được đưa ra mà bạn có thể sử dụng hầu hết thời gian (ít nhất là tôi làm):

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

Tuy nhiên, nếu my_list chứa các thùng chứa khác (ví dụ: danh sách lồng nhau), bạn phải sử dụng deepcopy như những thứ khác được đề xuất trong các câu trả lời ở trên từ thư viện sao chép. Ví dụ:

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

. Phần thưởng : Nếu bạn không muốn sao chép các yếu tố sử dụng (còn gọi là bản sao nông):

new_list = my_list[:]

Hãy hiểu sự khác biệt giữa Giải pháp số 1 và Giải pháp số 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])

Như bạn có thể thấy Giải pháp số 1 hoạt động hoàn hảo khi chúng tôi không sử dụng các danh sách lồng nhau. Hãy kiểm tra xem điều gì sẽ xảy ra khi chúng tôi áp dụng giải pháp số 1 cho các danh sách lồng nhau.

>>> 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

8

Lưu ý rằng có một số trường hợp nếu bạn đã xác định lớp tùy chỉnh của riêng mình và bạn muốn giữ các thuộc tính thì bạn nên sử dụng copy.copy()hoặc copy.deepcopy()thay vì các lựa chọn thay thế, ví dụ như trong Python 3:

import copy

class MyList(list):
    pass

lst = MyList([1,2,3])

lst.name = 'custom list'

d = {
'original': lst,
'slicecopy' : lst[:],
'lstcopy' : lst.copy(),
'copycopy': copy.copy(lst),
'deepcopy': copy.deepcopy(lst)
}


for k,v in d.items():
    print('lst: {}'.format(k), end=', ')
    try:
        name = v.name
    except AttributeError:
        name = 'NA'
    print('name: {}'.format(name))

Đầu ra:

lst: original, name: custom list
lst: slicecopy, name: NA
lst: lstcopy, name: NA
lst: copycopy, name: custom list
lst: deepcopy, name: custom list

5
new_list = my_list[:]

new_list = my_list Hãy cố gắng hiểu điều này. Giả sử my_list nằm trong bộ nhớ heap tại vị trí X, tức là my_list đang trỏ đến X. Bây giờ bằng cách chỉ định new_list = my_listbạn cho phép new_list trỏ vào X. Đây được gọi là Sao chép nông.

Bây giờ nếu bạn chỉ định new_list = my_list[:]Bạn chỉ cần sao chép từng đối tượng của my_list vào new_list. Điều này được gọi là bản sao sâu.

Cách khác bạn có thể làm điều này là:

  • new_list = list(old_list)
  • import copy new_list = copy.deepcopy(old_list)

3

Tôi muốn đăng một cái gì đó hơi khác một chút sau đó một số câu trả lời khác. Mặc dù đây rất có thể không phải là tùy chọn dễ hiểu nhất hoặc nhanh nhất, nhưng nó cung cấp một chút cái nhìn bên trong về cách thức sao chép sâu hoạt động, cũng như là một tùy chọn khác để sao chép sâu. Sẽ không có vấn đề gì nếu chức năng của tôi có lỗi, vì mục đích của việc này là chỉ ra một cách để sao chép các đối tượng như câu trả lời câu hỏi, mà còn sử dụng điều này như một điểm để giải thích cách thức hoạt động sâu của lõi.

Cốt lõi của bất kỳ chức năng sao chép sâu là cách để tạo một bản sao nông. Làm sao? Đơn giản. Bất kỳ chức năng sao chép sâu chỉ sao chép các thùng chứa của các đối tượng bất biến. Khi bạn phân tích sâu một danh sách lồng nhau, bạn chỉ sao chép các danh sách bên ngoài, không phải các đối tượng có thể thay đổi bên trong danh sách. Bạn chỉ đang nhân đôi các container. Các lớp học cũng vậy. Khi bạn phân tích sâu một lớp, bạn sẽ phân tích sâu tất cả các thuộc tính có thể thay đổi của nó. Rồi sao? Tại sao bạn chỉ phải sao chép các thùng chứa, như danh sách, dicts, tuples, iters, class và class dụ?

Thật đơn giản. Một đối tượng có thể thay đổi thực sự không thể được nhân đôi. Nó không bao giờ có thể thay đổi, vì vậy nó chỉ là một giá trị duy nhất. Điều đó có nghĩa là bạn không bao giờ phải nhân đôi chuỗi, số, bool hoặc bất kỳ chuỗi nào. Nhưng làm thế nào bạn sẽ nhân đôi các container? Đơn giản. Bạn chỉ cần khởi tạo một thùng chứa mới với tất cả các giá trị. Deepcopy dựa vào đệ quy. Nó sao chép tất cả các container, ngay cả những container có container bên trong chúng, cho đến khi không còn container nào. Một container là một đối tượng bất biến.

Một khi bạn biết rằng, sao chép hoàn toàn một đối tượng mà không có bất kỳ tài liệu tham khảo nào là khá dễ dàng. Đây là một chức năng để phân tích sâu các kiểu dữ liệu cơ bản (sẽ không hoạt động cho các lớp tùy chỉnh nhưng bạn luôn có thể thêm nó)

def deepcopy(x):
  immutables = (str, int, bool, float)
  mutables = (list, dict, tuple)
  if isinstance(x, immutables):
    return x
  elif isinstance(x, mutables):
    if isinstance(x, tuple):
      return tuple(deepcopy(list(x)))
    elif isinstance(x, list):
      return [deepcopy(y) for y in x]
    elif isinstance(x, dict):
      values = [deepcopy(y) for y in list(x.values())]
      keys = list(x.keys())
      return dict(zip(keys, values))

Bản đồ sâu tích hợp sẵn của Python dựa trên ví dụ đó. Sự khác biệt duy nhất là nó hỗ trợ các loại khác và cũng hỗ trợ các lớp người dùng bằng cách sao chép các thuộc tính vào một lớp trùng lặp mới và cũng chặn đệ quy vô hạn với một tham chiếu đến một đối tượng mà nó đã thấy bằng cách sử dụng danh sách ghi nhớ hoặc từ điển. Và đó thực sự là nó để tạo ra các bản sao sâu sắc. Tại cốt lõi của nó, làm cho một bản sao sâu chỉ là tạo ra các bản sao nông. Tôi hy vọng câu trả lời này thêm một cái gì đó cho câu hỏi.

VÍ DỤ

Giả sử bạn có danh sách này: [1, 2, 3] . Các số bất biến không thể được nhân đôi, nhưng lớp khác thì có thể. Bạn có thể nhân đôi nó bằng cách hiểu danh sách: [x for x in [1, 2, 3]

Bây giờ, hãy tưởng tượng bạn có danh sách này: [[1, 2], [3, 4], [5, 6]] . Lần này, bạn muốn tạo một hàm, sử dụng đệ quy để sao chép sâu tất cả các lớp của danh sách. Thay vì hiểu danh sách trước đó:

[x for x in _list]

Nó sử dụng một cái mới cho danh sách:

[deepcopy_list(x) for x in _list]

deepcopy_list trông như thế này:

def deepcopy_list(x):
  if isinstance(x, (str, bool, float, int)):
    return x
  else:
    return [deepcopy_list(y) for y in x]

Sau đó, bây giờ bạn có một hàm có thể phân tích sâu bất kỳ danh sách strs, bools, floast, ints và thậm chí liệt kê vô số các lớp bằng cách sử dụng đệ quy. Và ở đó bạn có nó, sâu sắc.

TLDR : Deepcopy sử dụng đệ quy để sao chép các đối tượng và chỉ trả về các đối tượng bất biến như trước đây, vì các đối tượng bất biến không thể được sao chép. Tuy nhiên, nó phân tích sâu các lớp bên trong nhất của các đối tượng có thể thay đổi cho đến khi nó đạt đến lớp có thể thay đổi ngoài cùng của một đối tượng.


3

Một viễn cảnh thực tế nhỏ để nhìn vào bộ nhớ thông qua id và gc.

>>> b = a = ['hell', 'word']
>>> c = ['hell', 'word']

>>> id(a), id(b), id(c)
(4424020872, 4424020872, 4423979272) 
     |           |
      -----------

>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # all referring to same 'hell'
     |           |           |
      -----------------------

>>> id(a[0][0]), id(b[0][0]), id(c[0][0])
(4422785208, 4422785208, 4422785208) # all referring to same 'h'
     |           |           |
      -----------------------

>>> a[0] += 'o'
>>> a,b,c
(['hello', 'word'], ['hello', 'word'], ['hell', 'word'])  # b changed too
>>> id(a[0]), id(b[0]), id(c[0])
(4424018384, 4424018384, 4424018328) # augmented assignment changed a[0],b[0]
     |           |
      -----------

>>> b = a = ['hell', 'word']
>>> id(a[0]), id(b[0]), id(c[0])
(4424018328, 4424018328, 4424018328) # the same hell
     |           |           |
      -----------------------

>>> import gc
>>> gc.get_referrers(a[0]) 
[['hell', 'word'], ['hell', 'word']]  # one copy belong to a,b, the another for c
>>> gc.get_referrers(('hell'))
[['hell', 'word'], ['hell', 'word'], ('hell', None)] # ('hello', None) 

3

Hãy nhớ rằng trong Python khi bạn làm:

    list1 = ['apples','bananas','pineapples']
    list2 = list1

List2 không lưu trữ danh sách thực tế, mà là một tham chiếu đến list1. Vì vậy, khi bạn làm bất cứ điều gì để list1, list2 cũng thay đổi. sử dụng mô-đun sao chép (không mặc định, tải xuống trên pip) để tạo bản sao gốc của danh sách ( copy.copy()đối với danh sách đơn giản, copy.deepcopy()đối với các danh sách lồng nhau). Điều này tạo ra một bản sao không thay đổi với danh sách đầu tiên.


1

Tùy chọn deepcopy là phương pháp duy nhất phù hợp với tôi:

from copy import deepcopy

a = [   [ list(range(1, 3)) for i in range(3) ]   ]
b = deepcopy(a)
b[0][1]=[3]
print('Deep:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]   ]
b = a*1
b[0][1]=[3]
print('*1:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ] ]
b = a[:]
b[0][1]=[3]
print('Vector copy:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = list(a)
b[0][1]=[3]
print('List copy:')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = a.copy()
b[0][1]=[3]
print('.copy():')
print(a)
print(b)
print('-----------------------------')
a = [   [ list(range(1, 3)) for i in range(3) ]  ]
b = a
b[0][1]=[3]
print('Shallow:')
print(a)
print(b)
print('-----------------------------')

dẫn đến đầu ra của:

Deep:
[[[1, 2], [1, 2], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
*1:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Vector copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
List copy:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
.copy():
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------
Shallow:
[[[1, 2], [3], [1, 2]]]
[[[1, 2], [3], [1, 2]]]
-----------------------------

1

Điều này là do, dòng new_list = my_listgán một tham chiếu mới cho biến my_listđó là new_list này cũng tương tự như Cđang đưa ra dưới đây,

int my_list[] = [1,2,3,4];
int *new_list;
new_list = my_list;

Bạn nên sử dụng mô-đun sao chép để tạo một danh sách mới bằng cách

import copy
new_list = copy.deepcopy(my_list)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.