Pytanie Jak sklonować lub skopiować listę?


Jakie są opcje klonowania lub kopiowania listy w Pythonie?

Za pomocą new_list = my_list następnie modyfikuje new_list każdego razu my_list zmiany.
Dlaczego to?


1698
2018-04-10 08:49


pochodzenie




Odpowiedzi:


Z new_list = my_list, w rzeczywistości nie masz dwóch list. Zadanie po prostu kopiuje odniesienie do listy, a nie do rzeczywistej listy, a więc do obu new_list i my_list po tej cesji odwołaj się do tej samej listy.

Aby skopiować listę, masz różne możliwości:

  • Możesz użyć wbudowanego list.copy() metoda (dostępna od Pythona 3.3):

    new_list = old_list.copy()
    
  • Możesz ją pokroić:

    new_list = old_list[:]
    

    Alexa Martelliego opinia (przynajmniej w 2007 roku) o tym jest, że jest to dziwna składnia i nie ma sensu jej używać. ;) (Jego zdaniem następny jest bardziej czytelny).

  • Możesz użyć wbudowanych list() funkcjonować:

    new_list = list(old_list)
    
  • Możesz użyć ogólnego copy.copy():

    import copy
    new_list = copy.copy(old_list)
    

    To trochę wolniej niż list() ponieważ musi znaleźć typ danych old_list pierwszy.

  • Jeśli lista zawiera obiekty i chcesz je również skopiować, użyj ogólnego copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)
    

    Oczywiście najwolniejsza i najbardziej wymagająca pamięci metoda, ale czasami nieunikniona.

Przykład:

import copy

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

    def __repr__(self):
        return str(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\n list.copy(): %r\n slice: %r\n list(): %r\n copy: %r\n deepcopy: %r'
      % (a, b, c, d, e, f))

Wynik:

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

2326
2018-04-10 08:55



@FelixKling: Czy ma sens edytowanie tej odpowiedzi, aby wspomnieć o list.copy metoda (dostępna od Python 3.3)? Jeśli nie potrzebujesz kompatybilności z Pythonem 2, naprawdę jest to jedyny oczywisty sposób na zrobienie tego. - Mark Dickinson
@FelixKling I 100% zgadzam się. Aby uzyskać odpowiedź na ważne pytanie w języku Python, ta jest nieco rozproszona i nieaktualna. - Jiminion
Jeśli się nie mylę: newlist = [*mylist] również jest możliwa w Pythonie 3. newlist = list(mylist) może jest jednak bardziej przejrzyste. - Stéphane
Inną możliwością jest nowa_lista = stara_lista * 1 - aris


Felix już dostarczył doskonałą odpowiedź, ale pomyślałem, że dokonam porównania prędkości różnych metod:

  1. 10,59 s (105,9us / itn) - copy.deepcopy(old_list)
  2. 10,16 sec (101,6us / itn) - czysty python Copy() metoda kopiowania klas za pomocą deepcopy
  3. 1,488 s (14,88us / itn) - czysty pyton Copy() metoda nie kopiująca klas (tylko dyktatury / listy / krotki)
  4. 0.325 sec (3.25us / itn) - for item in old_list: new_list.append(item)
  5. 0,217 s (2,17 us / itn) - [i for i in old_list] (za zrozumienie listy)
  6. 0,186 s (1,86us / itn) - copy.copy(old_list)
  7. 0,075 s (0,75us / itn) - list(old_list)
  8. 0.053 sec (0.53us / itn) - new_list = []; new_list.extend(old_list)
  9. 0,039 s (0,39us / itn) - old_list[:] (krojenie listy)

Najszybszy jest więc podział listy. Ale pamiętaj o tym copy.copy(), list[:] i list(list), w odróżnieniu copy.deepcopy() a wersja pythona nie kopiuje żadnych list, słowników i instancji klas na liście, więc jeśli oryginały ulegną zmianie, zmienią się również na liście skopiowanej i na odwrót.

(Oto skrypt, jeśli ktoś jest zainteresowany lub chce poruszyć jakieś problemy :)

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

EDYTOWAĆ: Dodano nowe style, klasy i dyktatury w starym stylu do benchmarków i znacznie przyspieszyłem wersję pythona i dodaliśmy więcej metod, w tym wyrażeń listowych i extend().


449
2018-04-10 10:16



Ponieważ przeprowadzasz testy porównawcze, pomocne może być dołączenie punktu odniesienia. Czy te liczby są nadal dokładne w 2017 roku, używając Pythona 3.6 z całkowicie skompilowanym kodem? Poniżej znajduje się odpowiedź (stackoverflow.com/a/17810305/26219) już kwestionuje tę odpowiedź. - Mark Edington
Użyj timeit moduł. nie możesz też wiele zawrzeć w arbitralnych mikro testach porównawczych, takich jak ten. - Corey Goldberg


Ja powiedziano ten Python 3.3+ dodaje list.copy() metoda, która powinna być tak szybka jak cięcie:

newlist = old_list.copy()


116
2017-07-23 12:32





Jakie są opcje klonowania lub kopiowania listy w Pythonie?

W języku Python 3 można wykonać płytką kopię za pomocą:

a_copy = a_list.copy()

W Pythonie 2 i 3 możesz uzyskać płytką kopię z pełnym wycinkiem oryginału:

a_copy = a_list[:]

Wyjaśnienie

Istnieją dwa semantyczne sposoby kopiowania listy. Płytka kopia tworzy nową listę tych samych obiektów, a głęboka kopia tworzy nową listę zawierającą nowe równoważne obiekty.

Płytka kopia listy

Płytka kopia kopiuje samą listę, która jest kontenerem odniesień do obiektów na liście. Jeśli zawarte w nich obiekty są zmienne, a jedna zmieniona, zmiana zostanie odzwierciedlona na obu listach.

Można to zrobić na różne sposoby w Pythonie 2 i 3. Python 2 działa również w Pythonie 3.

Python 2

W Pythonie 2, idiomatyczny sposób tworzenia płytkiej kopii listy jest z kompletnym wycinkiem oryginału:

a_copy = a_list[:]

Możesz również wykonać to samo, przekazując listę przez konstruktor listy,

a_copy = list(a_list)

ale użycie konstruktora jest mniej wydajne:

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

Python 3

W Pythonie 3 listy pobierają list.copy metoda:

a_copy = a_list.copy()

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

Robi się kolejny wskaźnik nie Zrób kopię

Używając new_list = my_list, modyfikuje new_list za każdym razem gdy my_list się zmienia. Dlaczego to?

my_list to tylko nazwa, która wskazuje na rzeczywistą listę w pamięci. Kiedy powiesz new_list = my_list nie robisz kopii, tylko dodajesz inną nazwę, która wskazuje na oryginalną listę w pamięci. Możemy mieć podobne problemy, gdy tworzymy kopie list.

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

Lista jest po prostu zbiorem wskazówek do treści, więc płytka kopia po prostu kopiuje wskaźniki, więc masz dwie różne listy, ale mają tę samą zawartość. Aby wykonać kopie zawartości, potrzebujesz głębokiej kopii.

Głębokie kopie

Zrobić głęboka kopia listy, w Pythonie 2 lub 3, użyj deepcopy w copy moduł:

import copy
a_deep_copy = copy.deepcopy(a_list)

Aby zademonstrować, w jaki sposób pozwala nam to tworzyć nowe listy podrzędne:

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

I tak widzimy, że lista głęboko skopiowana to zupełnie inna lista od oryginału. Możesz rzucić swoją własną funkcją - ale nie rób tego. Prawdopodobnie stworzysz błędy, których inaczej nie zrobiłbyś, używając funkcji głębokiej biblioteki standardowej.

Nie używaj eval

Możesz to zobaczyć jako sposób na głębszą kopie, ale nie rób tego:

problematic_deep_copy = eval(repr(a_list))
  1. Jest to niebezpieczne, zwłaszcza jeśli oceniasz coś ze źródła, któremu nie ufasz.
  2. Nie jest wiarygodny, jeśli podelement, który kopiujesz, nie ma reprezentacji, która mogłaby zostać wykorzystana do odtworzenia równoważnego elementu.
  3. Jest także mniej wydajny.

W 64-bitowym Pythonie 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

w 64-bitowym Pythonie 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

88
2017-10-25 12:13





Istnieje już wiele odpowiedzi, które mówią, jak utworzyć odpowiednią kopię, ale żadne z nich nie wyjaśnia, dlaczego nie powiodła się oryginalna "kopia".

Python nie przechowuje wartości w zmiennych; wiąże nazwy z obiektami. Twoje pierwotne zadanie zajęło obiekt, o którym mowa my_list i związać to new_list także. Niezależnie od tego, której nazwy używasz, wciąż jest tylko jedna lista, więc zmiany dokonane podczas jej oznaczania jako my_list będzie się utrzymywał, gdy będzie się go określać jako new_list. Każda z pozostałych odpowiedzi na to pytanie daje różne sposoby tworzenia nowego obiektu do powiązania new_list.

Każdy element listy działa jak nazwa, ponieważ każdy element wiąże się nie tylko z obiektem. Płytka kopia tworzy nową listę, której elementy wiążą się z tymi samymi obiektami co poprzednio.

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]

Aby zabrać listę, wykonaj jeszcze jeden krok, skopiuj każdy obiekt, do którego odnosi się lista, i powiąż kopie elementów z nową listą.

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

To jeszcze nie jest głęboka kopia, ponieważ każdy element listy może odnosić się do innych obiektów, tak jak lista jest związana z jej elementami. Aby rekurencyjnie skopiować każdy element na liście, a następnie każdy inny obiekt, do którego odnoszą się poszczególne elementy i tak dalej: wykonaj głęboką kopię.

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

Widzieć dokumentacja aby uzyskać więcej informacji o przypadkach narożnych podczas kopiowania.


42
2017-11-23 16:45





new_list = list(old_list)


30
2018-04-10 09:03





Posługiwać się thing[:]

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

27
2018-04-10 08:53





Mowa Pythona, aby to zrobić newList = oldList[:]


26
2018-04-10 08:53