Jak sklonować lub skopiować listę?

2543

Jakie są opcje klonowania lub kopiowania listy w Pythonie?

Podczas używania new_list = my_listwszelkie modyfikacje new_listzmian my_listza każdym razem. Dlaczego to?

aF.
źródło

Odpowiedzi:

3323

Dzięki new_list = my_listtak naprawdę nie masz dwóch list. Zadanie po prostu kopiuje odwołanie do listy, a nie do faktycznej listy, więc zarówno new_listimy_list odnoszą się do tej samej listy po cesji.

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

  • Możesz użyć wbudowanej list.copy()metody (dostępnej od Python 3.3):

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

    new_list = old_list[:]

    Opinia Alexa Martellego (przynajmniej w 2007 roku ) na ten temat jest taka, że jest to dziwna składnia i nigdy nie ma sensu jej używać . ;) (Jego zdaniem następny jest bardziej czytelny).

  • Możesz użyć wbudowanej list()funkcji:

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

    import copy
    new_list = copy.copy(old_list)

    Jest to trochę wolniejsze niż list()dlatego, że najpierw musi znaleźć typ danych old_list.

  • 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 czasem nieunikniona.

Przykład:

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

Wynik:

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)]
Felix Kling
źródło
7
Jeśli się nie mylę: newlist = [*mylist]istnieje również możliwość w Pythonie 3. Być newlist = list(mylist)może jest to jednak bardziej jasne.
Stéphane
9
inna możliwośc to new_list = old_list * 1
Aris
4
Które z tych metod są płytkie, a które głębokie?
Eswar
4
@Eswar: wszyscy oprócz ostatniego robią płytką kopię
Felix Kling
3
@Eswar to płytka kopia.
juanpa.arrivillaga
602

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

  1. 10,59 s (105,9US / itn) - copy.deepcopy(old_list)
  2. 10,16 s (101,6us / itn) - czysty python Copy() klasowe kopiowanie klas metodą pomocą deepcopy
  3. 1.488 sec (14.88us / itn) - czysty python Copy() metoda nie kopiująca klas (tylko dykta / listy / krotki)
  4. 0,325 s (3,25US / ITN) - for item in old_list: new_list.append(item)
  5. 0.217 s (2.17us / itn) - [i for i in old_list]( zrozumienie listy )
  6. 0,186 sek (1,86us / itn) - copy.copy(old_list)
  7. 0,075 s (0,75us / itn) - list(old_list)
  8. 0,053 s (0,53us / itn) - new_list = []; new_list.extend(old_list)
  9. 0,039 s (0,39us / itn) - old_list[:]( segmentacja list )

Najszybszy jest więc podział list. Ale należy pamiętać, że copy.copy(), list[:]i list(list), w przeciwieństwie do copy.deepcopy()a wersja Pythona nie kopiować żadnych list, słowniki i instancje klas w liście, więc jeśli oryginały zmieni, będą zmieniać się w skopiowanym liście też i odwrotnie.

(Oto skrypt, jeśli ktoś jest zainteresowany lub chce podnieść 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
krio
źródło
9
Ponieważ przeprowadzasz testy porównawcze, pomocne może być podanie punktu odniesienia. Czy liczby te są nadal dokładne w 2017 roku przy użyciu Pythona 3.6 z w pełni skompilowanym kodem? Zauważam, że odpowiedź poniżej ( stackoverflow.com/a/17810305/26219 ) już kwestionuje tę odpowiedź.
Mark Edington,
4
użyj timeitmodułu. ponadto nie można wyciągać zbyt wielu wniosków z dowolnych mikrowymiarowych testów porównawczych takich jak ten.
Corey Goldberg,
3
Jeśli chcesz dołączyć nową opcję dla wersji 3.5+, [*old_list]powinna ona być w przybliżeniu równoważna list(old_list), ale ponieważ jest ona składnią, a nie ogólnymi ścieżkami wywołań funkcji, zaoszczędzi trochę czasu wykonywania (i w przeciwieństwie do tego old_list[:], który nie wpisuje konwersji, [*old_list]działa na każdym iterowalnym i tworzy a list).
ShadowRanger
3
@CoreyGoldberg, aby zapoznać się z nieco mniej arbitralnym testem mikroprocesorowym (zastosowania timeit, przebiegi 50m zamiast 100k) patrz stackoverflow.com/a/43220129/3745896
Rzeka
1
@ShadowRanger [*old_list]faktycznie wydaje się przewyższać prawie każdą inną metodę. (patrz moja odpowiedź połączona z poprzednimi komentarzami)
Rzeka
125

Jakie są opcje klonowania lub kopiowania listy w Pythonie?

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

a_copy = a_list.copy()

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

a_copy = a_list[:]

Wyjaśnienie

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

Płytka kopia listy

Płytka kopia kopiuje tylko samą listę, która jest kontenerem odniesień do obiektów na liście. Jeśli zawarte w nich obiekty można modyfikować, a jeden zostanie zmieniony, zmiana zostanie odzwierciedlona na obu listach.

Istnieją różne sposoby, aby to zrobić w Python 2 i 3. Sposoby Python 2 będą również działać w Python 3.

Python 2

W Pythonie 2 idiomatycznym sposobem wykonania płytkiej kopii listy jest użycie pełnego wycinka oryginału:

a_copy = a_list[:]

Możesz również osiągnąć 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.copymetodę:

a_copy = a_list.copy()

W 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

Utworzenie innego wskaźnika nie powoduje wykonania kopii

Użycie nowej_listy = moja_lista następnie modyfikuje nową_listę za każdym razem, gdy zmienia się moja_lista. Dlaczego to?

my_listto tylko nazwa wskazująca na rzeczywistą listę w pamięci. Kiedy powiesznew_list = my_list że nie robisz kopii, dodajesz inną nazwę wskazującą na oryginalną listę w pamięci. Podobne problemy mogą występować podczas tworzenia kopii list.

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

Lista jest tylko tablicą wskaźników do zawartoś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ć kopię zawartości, potrzebujesz głębokiej kopii.

Głębokie kopie

Aby zrobić głęboką kopię listy, w Pythonie 2 lub 3, użyj deepcopyw copymodule :

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'], [], []]

Widzimy więc, że głęboko skopiowana lista jest zupełnie inną listą niż oryginał. Możesz rzucić własną funkcję - ale nie. Prawdopodobnie będziesz tworzyć błędy, których inaczej byś nie zrobił, korzystając ze standardowej funkcji kopiowania biblioteki.

Nie używaj eval

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

problematic_deep_copy = eval(repr(a_list))
  1. Jest to niebezpieczne, szczególnie jeśli oceniasz coś ze źródła, któremu nie ufasz.
  2. Nie jest to wiarygodne, jeśli kopiowany podelement nie ma reprezentacji, którą można ewaluować w celu odtworzenia równoważnego elementu.
  3. Jest również mniej wydajny.

W 64-bitowym języku 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

w 64-bitowym języku 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
Aaron Hall
źródło
1
Jeśli lista jest dwuwymiarowa, nie potrzebujesz głębokiej kopii. Jeśli jest to lista list, a te listy nie mają w sobie list, możesz użyć pętli for. Obecnie używam list_copy=[] for item in list: list_copy.append(copy(item))i jest znacznie szybszy.
John Locke,
53

Istnieje już wiele odpowiedzi, które mówią, jak zrobić właściwą kopię, ale żadna z nich nie wyjaśnia, dlaczego oryginalna „kopia” nie powiodła się.

Python nie przechowuje wartości w zmiennych; wiąże nazwy z obiektami. Twoje pierwotne zadanie obejmowało obiekt, o którym mowa, my_listi wiązało je new_listrównież. Bez względu na to, jakiej nazwy użyjesz, wciąż jest tylko jedna lista, więc zmiany dokonane w odniesieniu do niej my_listbędą obowiązywać, gdy będziesz ją 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 przesunąć listę o krok dalej, skopiuj każdy obiekt, do którego odnosi się twoja lista, i powiąż te 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]

Nie jest to jeszcze 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 odnosi się każdy element, i tak dalej: wykonaj głęboką kopię.

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

Zobacz dokumentację uzyskać więcej informacji o przypadkach narożnych w kopiowaniu.

Jacek
źródło
37

Posługiwać się thing[:]

>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>> 
Paul Tarjan
źródło
34

Zacznijmy od początku i zbadaj to pytanie.

Załóżmy, że masz dwie listy:

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

I musimy skopiować obie listy, zaczynając teraz od pierwszej listy:

Spróbujmy najpierw, ustawiając zmienną copyna naszą oryginalną listę list_1:

copy=list_1

Teraz, jeśli myślisz, że kopia skopiowała listę_1, to się mylisz. idFunkcja może pokazać nam, jeśli dwie zmienne mogą wskazywać na ten sam obiekt. Spróbujmy tego:

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

Dane wyjściowe to:

4329485320
4329485320

Obie zmienne są dokładnie tym samym argumentem. Czy jesteś zaskoczony?

Skoro wiemy, że Python nie przechowuje niczego w zmiennej, Zmienne odnoszą się tylko do obiektu, a obiekt przechowuje wartość. Tutaj przedmiotem jestlist ale utworzyliśmy dwa odwołania do tego samego obiektu przez dwie różne nazwy zmiennych. Oznacza to, że obie zmienne wskazują ten sam obiekt, tylko o różnych nazwach.

Kiedy to robisz copy=list_1, robi to:

wprowadź opis zdjęcia tutaj

Tutaj na liście obrazków_1 i kopii są dwie nazwy zmiennych, ale obiekt jest taki sam dla obu zmiennych, które są list

Więc jeśli spróbujesz zmodyfikować skopiowaną listę, zmodyfikuje ona również oryginalną listę, ponieważ lista jest tylko jedna, zmodyfikujesz tę listę bez względu na to, czy zrobisz to z listy skopiowanej lub z oryginalnej listy:

copy[0]="modify"

print(copy)
print(list_1)

wynik:

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

Więc zmodyfikował oryginalną listę:

Przejdźmy teraz do pythonowej metody kopiowania list.

copy_1=list_1[:]

Ta metoda rozwiązuje pierwszy problem, który mieliśmy:

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

4338792136
4338791432

Jak widzimy, nasza lista ma różne identyfikatory i oznacza to, że obie zmienne wskazują różne obiekty. Więc tak naprawdę dzieje się tutaj:

wprowadź opis zdjęcia tutaj

Teraz spróbujmy zmodyfikować listę i zobaczmy, czy nadal mamy do czynienia z poprzednim problemem:

copy_1[0]="modify"

print(list_1)
print(copy_1)

Dane wyjściowe to:

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

Jak widać, zmodyfikował tylko skopiowaną listę. To znaczy, że zadziałało.

Myślisz, że skończyliśmy? Nie. Spróbujmy skopiować naszą listę zagnieżdżoną.

copy_2=list_2[:]

list_2powinien odnosić się do innego obiektu, który jest kopią list_2. Sprawdźmy:

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

Otrzymujemy wynik:

4330403592 4330403528

Teraz możemy założyć, że obie listy wskazują inny obiekt, więc spróbujmy go zmodyfikować i zobaczmy, że daje to, co chcemy:

copy_2[0][1]="modify"

print(list_2,copy_2)

To daje nam wynik:

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

Może się to wydawać nieco mylące, ponieważ zadziałała ta sama metoda, którą poprzednio stosowaliśmy. Spróbujmy to zrozumieć.

Kiedy to zrobisz:

copy_2=list_2[:]

Kopiujesz tylko listę zewnętrzną, a nie wewnętrzną. Możemy użyć tej idfunkcji jeszcze raz, aby to sprawdzić.

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

Dane wyjściowe to:

4329485832
4329485832

Kiedy to robimy copy_2=list_2[:], dzieje się tak:

wprowadź opis zdjęcia tutaj

Tworzy kopię listy, ale tylko zewnętrzną kopię listy, a nie kopię listy zagnieżdżonej, lista zagnieżdżona jest taka sama dla obu zmiennych, więc jeśli spróbujesz zmodyfikować listę zagnieżdżoną, zmodyfikuje również oryginalną listę, ponieważ obiekt listy zagnieżdżonej jest taki sam dla obu list.

Jakie jest rozwiązanie? Rozwiązaniem jest deepcopyfunkcja.

from copy import deepcopy
deep=deepcopy(list_2)

Sprawdźmy to:

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

4322146056 4322148040

Obie listy zewnętrzne mają różne identyfikatory, spróbujmy tego na wewnętrznych listach zagnieżdżonych.

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

Dane wyjściowe to:

4322145992
4322145800

Jak widać oba identyfikatory są różne, co oznacza, że ​​możemy założyć, że obie listy zagnieżdżone wskazują teraz inny obiekt.

Oznacza to, że kiedy robisz to, deep=deepcopy(list_2)co się naprawdę dzieje:

wprowadź opis zdjęcia tutaj

Obie listy zagnieżdżone wskazują inny obiekt i mają teraz osobną kopię listy zagnieżdżonej.

Teraz spróbujmy zmodyfikować listę zagnieżdżoną i zobaczmy, czy rozwiązała poprzedni problem, czy nie:

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

Wyprowadza:

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

Jak widać, nie zmodyfikował oryginalnej listy zagnieżdżonej, tylko zmodyfikował skopiowaną listę.

Aaditya Ura
źródło
33

Idiomem tego języka jest Python newList = oldList[:]

erisco
źródło
33

Czasy Python 3.6

Oto wyniki synchronizacji przy użyciu Pythona 3.6.8. Pamiętaj, że czasy te są względem siebie względne, a nie absolutne.

Trzymałem się tylko robienia płytkich kopii, a także dodałem kilka nowych metod, które nie były możliwe w Python2, takich jak list.copy()( odpowiednik wycinka Python3 ) i dwie formy rozpakowywania listy ( *new_list, = listi new_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

Widzimy, że zwycięzca Python2 nadal dobrze sobie radzi, ale nie wyrówna Python3 list.copy() zbyt wiele , zwłaszcza biorąc pod uwagę jego lepszą czytelność.

Ciemny koń to metoda rozpakowywania i przepakowywania ( b = [*a]), która jest ~ 25% szybsza niż surowe krojenie i ponad dwukrotnie szybsza niż inna metoda rozpakowywania (*b, = a ).

b = a * 1 robi też zaskakująco dobrze.

Zauważ, że te metody nie dają równoważnych wyników dla danych wejściowych innych niż listy. Wszystkie działają dla obiektów, które można wycinać, kilka dla dowolnej iterowalnej, ale copy.copy()działa tylko dla bardziej ogólnych obiektów Pythona.


Oto kod testowy dla zainteresowanych stron ( szablon stąd ):

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))
Rzeka
źródło
1
Potrafi potwierdzić podobną historię z 3.8 b=[*a]- jedyny oczywisty sposób, aby to zrobić;).
SuperShoot
19

Wszyscy inni współautorzy udzielili świetnych odpowiedzi, które działają, gdy masz listę z jednym wymiarem (wyrównywaną), jednak metody wspomniane do tej pory copy.deepcopy()działają tylko w celu klonowania / kopiowania listy, a nie wskazują na zagnieżdżone listobiekty, gdy jesteś praca z wielowymiarowymi, zagnieżdżonymi listami (lista list). Podczas gdy Felix Kling odwołuje się do tego w swojej odpowiedzi, jest nieco więcej w tym problemie i możliwe jest obejście problemu za pomocą wbudowanych rozwiązań, które mogą okazać się szybszą alternatywą deepcopy.

Chociaż new_list = old_list[:], copy.copy(old_list)'a dla Py3k old_list.copy()pracy na listach pojedynczych wyrównane, oni przywrócić wskazując na listobiekty zagnieżdżone w obrębie old_lista new_list, a zmiany do jednego z listobiektów są utrwalać w drugiej.

Edycja: Nowe informacje ujawnione

Jak zauważyli zarówno Aaron Hall, jak i PM 2Ring, używanie eval()nie jest tylko złym pomysłem, ale jest również znacznie wolniejsze niż copy.deepcopy().

Oznacza to, że w przypadku list wielowymiarowych jedyną opcją jest copy.deepcopy(). Biorąc to pod uwagę, tak naprawdę nie jest to możliwe, ponieważ wydajność idzie daleko na południe, gdy próbujesz użyć go na średniej wielkości tablicy wielowymiarowej. Starałem siętimeit użyć tablicy 42x42, która nie była niespotykana ani nawet tak duża w aplikacjach bioinformatycznych. Zrezygnowałem z oczekiwania na odpowiedź i zacząłem pisać swoją edycję w tym poście.

Wydaje się, że jedyną realną opcją jest wówczas inicjalizacja wielu list i praca nad nimi niezależnie. Jeśli ktoś ma inne sugestie, jak radzić sobie z wielowymiarowym kopiowaniem list, byłoby to mile widziane.

Jak stwierdzili inni, przy korzystaniu z modułu i list wielowymiarowych występują poważne problemy z wydajnością .copycopy.deepcopy

AMR
źródło
5
Nie zawsze będzie to działać, ponieważ nie ma gwarancji, że zwrócony ciąg znaków repr()wystarczy do odtworzenia obiektu. Ponadto, eval()jest narzędziem ostateczności; zobacz Eval naprawdę niebezpieczny dla weterana SO Neda Batcheldera, aby uzyskać szczegółowe informacje. Dlatego kiedy opowiadasz się za użyciem eval(), naprawdę powinieneś wspomnieć, że może być niebezpieczne.
PM 2,
1
Uczciwy punkt. Chociaż myślę, że Batchelder ma na myśli to, że eval()ogólnie posiadanie funkcji w Pythonie jest ryzykowne. Nie chodzi o to, czy używasz funkcji w kodzie, ale o to, że jest to luka bezpieczeństwa w Pythonie sama w sobie. Mój przykład nie używa go z funkcji, która odbiera sygnał z input(), sys.agrvlub nawet pliku tekstowego. Bardziej przypomina to jednorazowe inicjowanie pustej listy wielowymiarowej, a następnie kopiowanie jej w pętli zamiast ponownego inicjowania przy każdej iteracji pętli.
AMR,
1
Jak zauważył @AaronHall, prawdopodobnie występuje znaczny problem z wydajnością new_list = eval(repr(old_list)), więc poza tym, że jest to zły pomysł, prawdopodobnie działa zbyt wolno.
AMR,
12

Zaskakuje mnie, że o tym jeszcze nie wspomniano, więc dla kompletności ...

Możesz wykonać rozpakowanie listy za pomocą „operatora splat”:, *który również skopiuje elementy listy.

old_list = [1, 2, 3]

new_list = [*old_list]

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

Oczywistym minusem tej metody jest to, że jest ona dostępna tylko w Python 3.5+.

Jeśli chodzi o czas, wydaje się, że działa to lepiej niż inne popularne metody.

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)
SCB
źródło
1
Jak zachowuje się ta metoda podczas modyfikowania kopii?
not2qubit
2
@ not2qubit masz na myśli dołączanie lub edytowanie elementów nowej listy. W przykładzie old_listi new_listsą dwie różne listy, edytując jeden nie zmieni drugiego (chyba że jesteś bezpośrednio mutacji same elementy (takie jak listy listy), żadna z tych metod, są głębokie kopie).
SCB,
7

W już udzielonych odpowiedziach brakowało bardzo prostego podejścia niezależnego od wersji Pythona, z którego można korzystać przez większość czasu (przynajmniej ja):

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

Jeśli jednak moja_lista zawiera inne kontenery (np. Listy zagnieżdżone), musisz użyć deepcopy, jak sugerują inni w powyższych odpowiedziach z biblioteki kopii. Na przykład:

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

. Premia : Jeśli nie chcesz kopiować elementów, użyj (czyli płytkiej kopii):

new_list = my_list[:]

Rozumiemy różnicę między rozwiązaniem nr 1 a rozwiązaniem nr 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])

Jak widać Rozwiązanie nr 1 działało idealnie, gdy nie korzystaliśmy z list zagnieżdżonych. Sprawdźmy, co się stanie, gdy zastosujemy rozwiązanie nr 1 do zagnieżdżonych list.

>>> 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
jainashish
źródło
7

Zauważ, że istnieją przypadki, w których jeśli zdefiniowałeś własną klasę niestandardową i chcesz zachować atrybuty, powinieneś użyć copy.copy()lub copy.deepcopy()zamiast alternatyw, na przykład w Pythonie 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))

Wyjścia:

lst: original, name: custom list
lst: slicecopy, name: NA
lst: lstcopy, name: NA
lst: copycopy, name: custom list
lst: deepcopy, name: custom list
Chris_Rands
źródło
4
new_list = my_list[:]

new_list = my_list Spróbuj to zrozumieć. Powiedzmy, że moja_lista znajduje się w pamięci sterty w lokalizacji X, tzn. Moja_lista wskazuje na X. Teraz poprzez przypisanienew_list = my_list Ci Pozwalanie nowej_listy wskazuje na X. Jest to znane jako płytka kopia.

Teraz, jeśli przypisasz new_list = my_list[:], po prostu kopiujesz każdy obiekt z mojej_listy na nową_listę. Jest to znane jako Deep copy.

Innym sposobem na to jest:

  • new_list = list(old_list)
  • import copy new_list = copy.deepcopy(old_list)
Ravi Shankar
źródło
2

Chciałem opublikować coś nieco innego niż niektóre inne odpowiedzi. Mimo że najprawdopodobniej nie jest to najbardziej zrozumiała lub najszybsza opcja, zapewnia ona nieco wewnętrzny obraz działania głębokiej kopii, a także jest inną alternatywą dla głębokiego kopiowania. Naprawdę nie ma znaczenia, czy moja funkcja ma błędy, ponieważ chodzi o to, aby pokazać sposób kopiowania obiektów, takich jak odpowiedzi na pytania, ale także użyć tego jako punktu, aby wyjaśnić, jak działa funkcja głębokiego kopiowania.

U podstaw każdej funkcji głębokiego kopiowania leży sposób na wykonanie płytkiej kopii. W jaki sposób? Prosty. Każda funkcja głębokiego kopiowania powiela jedynie kontenery niezmiennych obiektów. Podczas głębokiego kopiowania listy zagnieżdżonej kopiowane są tylko listy zewnętrzne, a nie zmienne obiekty wewnątrz list. Kopiujesz tylko pojemniki. To samo działa również w przypadku klas. Kiedy głęboko kopiujesz klasę, kopiujesz wszystkie jej zmienne atrybuty. Więc jak? Dlaczego musisz kopiować tylko kontenery, takie jak listy, dykty, krotki, itery, klasy i instancje klas?

To proste. Zmiennego obiektu nie można tak naprawdę powielić. Nigdy nie można go zmienić, więc jest to tylko jedna wartość. Oznacza to, że nigdy nie musisz duplikować ciągów, liczb, booli ani żadnego z nich. Ale jak powielibyście pojemniki? Prosty. Dokonujesz właśnie inicjalizacji nowego kontenera ze wszystkimi wartościami. Deepcopy polega na rekurencji. Powielają wszystkie pojemniki, nawet te z pojemnikami w nich, dopóki nie pozostaną żadne pojemniki. Kontener jest niezmiennym przedmiotem.

Kiedy już to wiesz, całkowite powielenie obiektu bez żadnych odniesień jest dość łatwe. Oto funkcja do głębokiego kopiowania podstawowych typów danych (nie działałaby dla klas niestandardowych, ale zawsze można to dodać)

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

Wbudowana głęboka kopia Pythona oparta jest na tym przykładzie. Jedyną różnicą jest to, że obsługuje inne typy, a także obsługuje klasy użytkowników poprzez duplikowanie atrybutów w nową zduplikowaną klasę, a także blokuje nieskończoną rekurencję w odniesieniu do obiektu, który już widział za pomocą listy notatek lub słownika. I to naprawdę wszystko, aby robić głębokie kopie. U podstaw robienia głębokiej kopii jest po prostu robienie płytkich kopii. Mam nadzieję, że ta odpowiedź doda coś do pytania.

PRZYKŁADY

Powiedz, że masz tę listę: [1, 2, 3] . Niezmiennych liczb nie można powielać, ale drugą warstwę można. Możesz go zduplikować, używając rozumienia listy: [x dla x w [1, 2, 3]

Teraz wyobraź sobie, że masz tę listę: [[1, 2], [3, 4], [5, 6]] . Tym razem chcesz stworzyć funkcję, która używa rekurencji do głębokiego kopiowania wszystkich warstw listy. Zamiast poprzedniego zrozumienia listy:

[x for x in _list]

Używa nowego dla list:

[deepcopy_list(x) for x in _list]

I deepcopy_list wygląda następująco:

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

Teraz masz funkcję, która może głęboko kopiować dowolną listę łańcuchów, bool, floast, ints, a nawet list do nieskończenie wielu warstw za pomocą rekurencji. I oto masz, głębokie kopiowanie.

TLDR : Deepcopy używa rekurencji do powielania obiektów i jedynie zwraca te same obiekty niezmienne jak poprzednio, ponieważ obiektów niezmiennych nie można powielić. Jednak wykonuje głębokie kopie najbardziej wewnętrznych warstw obiektów podlegających zmianom, dopóki nie osiągnie najbardziej zewnętrznej możliwej do zmiany warstwy obiektu.

Corman
źródło
2

Lekka praktyczna perspektywa spojrzenia w pamięć poprzez id i 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) 
B.Mr.W.
źródło
2

Pamiętaj o tym w Pythonie, gdy:

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

List2 nie przechowuje faktycznej listy, ale odniesienie do list1. Więc kiedy robisz cokolwiek do list1, lista2 również się zmienia. użyj modułu kopiowania (nie domyślnie, pobierz na pip), aby wykonać oryginalną kopię listy ( copy.copy()dla list prostych, copy.deepcopy()dla zagnieżdżonych). To tworzy kopię, która nie zmienia się z pierwszą listą.

Dr. Hippo
źródło
0

Opcja głębokiej kopii jest jedyną metodą, która działa dla mnie:

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('-----------------------------')

prowadzi do wyjścia:

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]]]
-----------------------------
shahar_m
źródło