Vraag Hoe een lijst te klonen of kopiëren?


Wat zijn de opties om een ​​lijst in Python te klonen of kopiëren?

Gebruik makend van new_list = my_list wijzigt vervolgens new_list elke keer my_list veranderingen.
Waarom is dit?


1690
2018-04-10 08:49


oorsprong


antwoorden:


Met new_list = my_list, je hebt eigenlijk geen twee lijsten. De opdracht kopieert alleen de verwijzing naar de lijst, niet de werkelijke lijst, dus beide new_list en my_list verwijs naar dezelfde lijst na de opdracht.

Om de lijst daadwerkelijk te kopiëren, hebt u verschillende mogelijkheden:

  • Je kunt de ingebouwde gebruiken list.copy() methode (beschikbaar sinds python 3.3):

    new_list = old_list.copy()
    
  • Je kunt het opdelen:

    new_list = old_list[:]
    

    Alex Martelli's mening (tenminste terug in 2007) hierover is dat het is een rare syntaxis en het heeft geen zin om het ooit te gebruiken. ;) (Naar zijn mening is de volgende beter leesbaar).

  • U kunt de ingebouwde gebruiken list() functie:

    new_list = list(old_list)
    
  • U kunt generiek gebruiken copy.copy():

    import copy
    new_list = copy.copy(old_list)
    

    Dit is iets langzamer dan list() omdat het het datatype van moet weten old_list eerste.

  • Als de lijst objecten bevat en u deze ook wilt kopiëren, gebruik dan generiek copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)
    

    Duidelijk de langzaamste en meest geheugen-behoevende methode, maar soms onvermijdelijk.

Voorbeeld:

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

Resultaat:

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

2315
2018-04-10 08:55



Felix leverde al een uitstekend antwoord, maar ik dacht dat ik een snelheidsvergelijking zou doen van de verschillende methoden:

  1. 10.59 sec (105.9us / itn) - copy.deepcopy(old_list)
  2. 10.16 sec (101.6us / itn) - pure python Copy() methode kopiëren van klassen met deepcopy
  3. 1.488 sec (14.88us / itn) - pure python Copy() methode geen klassen te kopiëren (alleen dicts / lijsten / tuples)
  4. 0.325 sec (3.25us / itn) - for item in old_list: new_list.append(item)
  5. 0.217 sec (2.17us / itn) - [i for i in old_list] (een lijst begrip)
  6. 0,186 sec (1,86us / itn) - copy.copy(old_list)
  7. 0.075 sec (0.75us / itn) - list(old_list)
  8. 0.053 sec (0.53us / itn) - new_list = []; new_list.extend(old_list)
  9. 0.039 sec (0.39us / itn) - old_list[:] (lijstsnijden)

Het snelste is lijstschaven. Maar let op dat copy.copy(), list[:] en list(list), in tegenstelling tot copy.deepcopy() en de python-versie kopieert geen lijsten, woordenboeken en klasseninstanties in de lijst, dus als de originelen veranderen, veranderen ze ook in de gekopieerde lijst en omgekeerd.

(Hier is het script als iemand geïnteresseerd is of problemen wil melden :)

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

BEWERK: Nieuwe stijl, ouderwetse klassen en dictaten toegevoegd aan de benchmarks, en de python-versie veel sneller gemaakt en nog wat meer methoden toegevoegd, waaronder lijstuitdrukkingen en extend().


447
2018-04-10 10:16



ik heb werd gezegd die Python 3.3+ voegt list.copy() methode, die net zo snel moet zijn als slicen:

newlist = old_list.copy()


116
2017-07-23 12:32



Wat zijn de opties om een ​​lijst in Python te klonen of kopiëren?

In Python 3 kan een ondiepe kopie worden gemaakt met:

a_copy = a_list.copy()

In Python 2 en 3 kun je een ondiep exemplaar krijgen met een volledige plak van het origineel:

a_copy = a_list[:]

Uitleg

Er zijn twee semantische manieren om een ​​lijst te kopiëren. Een ondiepe kopie maakt een nieuwe lijst van dezelfde objecten, een diepe kopie maakt een nieuwe lijst met nieuwe equivalente objecten.

Ondiepe lijst kopiëren

Een ondiepe kopie kopieert alleen de lijst zelf. Dit is een container met verwijzingen naar de objecten in de lijst. Als de objecten die zichzelf bevatten, kunnen worden gewijzigd en één is gewijzigd, wordt de wijziging in beide lijsten weergegeven.

Er zijn verschillende manieren om dit te doen in Python 2 en 3. De Python 2-manieren werken ook in Python 3.

Python 2

In Python 2 is de idiomatische manier om een ​​ondiepe kopie van een lijst te maken een complete plak van het origineel:

a_copy = a_list[:]

U kunt hetzelfde ook bereiken door de lijst door de lijstconstructeur te leiden,

a_copy = list(a_list)

maar het gebruik van de constructor is minder efficiënt:

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

Python 3

In Python 3 krijgen lijsten de list.copy methode:

a_copy = a_list.copy()

In 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

Het maken van een andere aanwijzer doet dat niet een kopie maken

Gebruik new_list = mijn_lijst en wijzig vervolgens new_list elke keer dat mijn_lijst verandert. Waarom is dit?

my_list is slechts een naam die naar de daadwerkelijke lijst in het geheugen verwijst. Wanneer je zegt new_list = my_list je maakt geen kopie, je voegt gewoon een andere naam toe die op die originele lijst in het geheugen wijst. We kunnen soortgelijke problemen hebben wanneer we kopieën van lijsten maken.

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

De lijst is slechts een reeks aanwijzingen naar de inhoud, dus een ondiepe kopie kopieert alleen de aanwijzers en u hebt dus twee verschillende lijsten, maar ze hebben dezelfde inhoud. Om kopieën van de inhoud te maken, hebt u een diepkopie nodig.

Diepe kopieën

Om een ​​te maken diepe kopie van een lijst, in Python 2 of 3, gebruiken deepcopy in de copy module:

import copy
a_deep_copy = copy.deepcopy(a_list)

Om te laten zien hoe we nieuwe sublijsten kunnen maken:

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

En dus zien we dat de diep gekopieerde lijst een geheel andere lijst is dan het origineel. Je zou je eigen functie kunnen rollen - maar doe dat niet. U maakt waarschijnlijk fouten die u anders niet zou hebben door de deepcopy-functie van de standaardbibliotheek te gebruiken.

Niet gebruiken eval

Je zou dit kunnen gebruiken als een manier om te deepcopy, maar doe het niet:

problematic_deep_copy = eval(repr(a_list))
  1. Het is gevaarlijk, vooral als je iets evalueert van een bron die je niet vertrouwt.
  2. Het is niet betrouwbaar, als een subelement dat u kopieert geen representatie heeft die kan worden geëvalueerd om een ​​equivalent element te reproduceren.
  3. Het is ook minder performant.

In 64 bit 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

op 64-bits 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

87
2017-10-25 12:13



Er zijn al veel antwoorden die u vertellen hoe u een goede kopie kunt maken, maar geen van hen zegt waarom uw oorspronkelijke 'kopie' is mislukt.

Python slaat geen waarden op in variabelen; het bindt namen aan objecten. Je oorspronkelijke opdracht heeft het object waarnaar verwezen wordt door my_list en gebonden aan new_list ook. Welke naam je ook gebruikt, er is nog steeds maar één lijst, dus de wijzigingen die je aanbrengt, verwijzen ernaar my_list blijft bestaan ​​als u ernaar verwijst new_list. Elk van de andere antwoorden op deze vraag geeft u verschillende manieren om een ​​nieuw object te maken om aan te binden new_list.

Elk element van een lijst gedraagt ​​zich als een naam, omdat elk element niet-exclusief aan een object bindt. Een ondiepe kopie maakt een nieuwe lijst waarvan de elementen binden aan dezelfde objecten als voorheen.

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]

Om een ​​kopie van uw lijst nog een keer te nemen, kopieert u elk object waarnaar uw lijst verwijst en koppelt u die elementkopieën aan een nieuwe lijst.

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

Dit is nog geen diepe kopie, omdat elk element van een lijst naar andere objecten kan verwijzen, net zoals de lijst aan zijn elementen is gebonden. Om recursief elk element in de lijst te kopiëren, en vervolgens elk ander object waarnaar door elk element wordt verwezen, enzovoort: voer een diepe kopie uit.

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

Zien de documentatie voor meer informatie over hoekgevallen bij het kopiëren.


42
2017-11-23 16:45



new_list = list(old_list)


30
2018-04-10 09:03



Gebruik thing[:]

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

27
2018-04-10 08:53



Het idioom van Python om dit te doen is newList = oldList[:]


26
2018-04-10 08:53



Alle andere bijdragers gaven Super goed antwoorden, die werken als u een lijst met een enkele dimensie (genivelleerd) heeft, echter alleen van de methoden die tot nu toe zijn genoemd copy.deepcopy() werkt om een ​​lijst te klonen / kopiëren en niet te laten wijzen op de geneste list objecten wanneer u werkt met multidimensionale, geneste lijsten (lijst met lijsten). Terwijl Felix Kling verwijst naar het in zijn antwoord, er is een beetje meer aan de kwestie en mogelijk een oplossing met behulp van ingebouwde ins die misschien een sneller alternatief voor deepcopy.

Terwijl new_list = old_list[:], copy.copy(old_list)' en voor Py3k old_list.copy() werk voor lijsten met één niveau, ze keren terug naar de list objecten die zijn genest in de old_list en de new_listen wijzigingen in een van de list objecten worden bestendigd in de andere.

Bewerken: nieuwe informatie aan het licht gebracht

Zoals door beide is aangegeven Aaron Hall en PM 2Ring  gebruik makend van eval() is niet alleen een slecht idee, het is ook veel langzamer dan copy.deepcopy(). 

Dit betekent dat voor multidimensionale lijsten de enige optie is copy.deepcopy(). Dat gezegd hebbende, het is echt geen optie, omdat de uitvoering veel zuidwaarts gaat wanneer je het op een multidimensionale array van gemiddelde grootte probeert te gebruiken. ik probeerde te timeit met behulp van een 42x42-array, niet ongehoord of zelfs zo groot voor toepassingen in bio-informatica, en ik gaf het wachten op een antwoord op en begon net mijn bewerking in dit bericht te typen.

Het lijkt erop dat de enige echte optie is om meerdere lijsten te initialiseren en hieraan onafhankelijk te werken. Als iemand andere suggesties heeft voor het omgaan met multidimensionale lijstkopieën, zou het op prijs worden gesteld.

Zoals anderen hebben gezegd, daar kan zijn  zijn significant prestatieproblemen met behulp van de copy module en copy.deepcopy  voor multidimensionale lijsten.  Probeer een andere manier uit te werken om de multidimensionale lijst te kopiëren zonder te gebruiken deepcopy, (Ik werkte aan een probleem voor een cursus die slechts 5 seconden toestaat voor het hele algoritme om krediet te ontvangen), ik bedacht een manier om ingebouwde functies te gebruiken om een ​​kopie van de geneste lijst te maken zonder ze naar elkaar of naar de list objecten die in hen zijn genest. ik gebruikte eval() en repr() in de toewijzing om de kopie van de oude lijst in de nieuwe lijst te maken zonder een koppeling naar de oude lijst te maken. Het heeft de vorm van:

new_list = eval(repr(old_list))

In principe is wat dit doet een vertegenwoordiging van old_list als een tekenreeks en evalueert vervolgens de tekenreeks alsof het het object is dat de tekenreeks vertegenwoordigt. Door dit te doen, geen link naar het origineel list object is gemaakt. Een nieuw list object wordt gemaakt en elke variabele verwijst naar zijn eigen onafhankelijke object. Hier is een voorbeeld met behulp van een 2 dimensionale geneste lijst.

old_list = [[0 for j in range(y)] for i in range(x)] # initialize (x,y) nested list

# assign a copy of old_list to new list without them pointing to the same list object
new_list = eval(repr(old_list)) 

# make a change to new_list 
for j in range(y):
    for i in range(x):
    new_list[i][j] += 1

Als u vervolgens de inhoud van elke lijst controleert, bijvoorbeeld een lijst van 4 bij 3, keert Python terug

>>> new_list

[[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]

>>> old_list

[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

Hoewel dit waarschijnlijk niet de canonieke of syntactisch correcte manier is om het te doen, lijkt het goed te werken. Ik heb de prestaties niet getest, maar dat ga ik raden eval() en rep() zal minder overhead hebben om te rennen dan deepcopy zullen.


18
2017-07-10 03:51



Python 3.6.0 Timings

Dit zijn de timingresultaten met Python 3.6.0. Houd in gedachten dat deze tijden relatief zijn ten opzichte van elkaar, niet absoluut.

Ik bleef alleen ondiepe kopieën maken en voegde ook een aantal nieuwe methoden toe die niet mogelijk waren in Python2, zoals list.copy() (de Python3 plak equivalent) en lijst met uitpakken (*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

We kunnen zien dat de oude winnaar nog steeds bovenaan komt, maar niet echt door een enorme hoeveelheid, gezien de verbeterde leesbaarheid van de Python3 list.copy() nadering.

Merk op dat deze methoden dat wel doen niet output gelijkwaardige resultaten voor een andere invoer dan lijsten. Ze werken allemaal voor snijdbare objecten, een paar werken voor elk iterable, maar alleen copy.copy() werkt voor elk Python-object.


Hier is de testcode voor geïnteresseerde partijen (Sjabloon van hier):

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

12
2018-04-05 01:01



In tegenstelling tot andere talen die hebben variabele en waarde, Python heeft naam en object.

Deze verklaring:

a = [1,2,3]

betekent om de lijst (object) een naam te geven a, en dit:

b = a

geeft gewoon hetzelfde object a een nieuwe naam b, dus wanneer je iets doet met a, het object verandert en daarom b veranderingen.

De enige manier om een ​​te maken werkelijk kopie van een is naar maak een nieuw object zoals andere antwoorden al hebben gezegd.

U kunt hier meer over zien hier.


11
2018-03-23 12:32