Come clonare o copiare un elenco?


2550

Quali sono le opzioni per clonare o copiare un elenco in Python?

Durante l'utilizzo new_list = my_list, ogni modifica alle modifiche new_listcambia my_listogni volta. Perchè è questo?

Risposte:


3330

Con new_list = my_list, in realtà non hai due elenchi. L'assegnazione copia semplicemente il riferimento all'elenco, non l'elenco effettivo, quindi entrambi new_liste my_listfanno riferimento allo stesso elenco dopo l'assegnazione.

Per copiare effettivamente l'elenco, hai varie possibilità:

  • Puoi usare il list.copy()metodo incorporato (disponibile da Python 3.3):

    new_list = old_list.copy()
  • Puoi tagliarlo:

    new_list = old_list[:]

    L' opinione di Alex Martelli (almeno nel 2007 ) su questo è che è una strana sintassi e non ha senso usarla mai . ;) (Secondo lui, il prossimo è più leggibile).

  • È possibile utilizzare la list()funzione integrata:

    new_list = list(old_list)
  • Puoi usare generico copy.copy():

    import copy
    new_list = copy.copy(old_list)

    Questo è un po 'più lento di list()perché deve prima scoprire il tipo di dati old_list.

  • Se l'elenco contiene oggetti e si desidera copiarli, utilizzare generico copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)

    Ovviamente il metodo più lento e che richiede memoria, ma a volte inevitabile.

Esempio:

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

Risultato:

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
Se non sbaglio: newlist = [*mylist]anche in Python 3. newlist = list(mylist)è possibile anche se forse è più chiaro.
Stéphane,

9
un'altra possibilità è new_list = old_list * 1
aris

4
Quali di questi metodi sono copia superficiale e quali di essi sono copia profonda?
Eswar,

4
@Eswar: tutti tranne l'ultimo fanno una copia superficiale
Felix Kling il

3
@Eswar è una copia superficiale.
juanpa.arrivillaga,

604

Felix ha già fornito una risposta eccellente, ma ho pensato di fare un confronto veloce dei vari metodi:

  1. 10,59 sec (105,9us / itn) - copy.deepcopy(old_list)
  2. 10,16 sec (101,6us / itn) - Copy()metodo Python puro che copia le classi con deepcopy
  3. 1,448 sec (14,88 us / itn) - Copy()metodo Python puro che non copia le classi (solo dicts / liste / tuple)
  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](una comprensione della lista )
  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[:]( elenco di sezioni )

Quindi il più veloce è il taglio delle liste. Ma essere consapevoli che copy.copy(), list[:]e list(list), a differenza copy.deepcopy()e la versione di Python non copiare alcun elenco, dizionari e istanze di classe nella lista, quindi se gli originali cambiano, cambieranno nella lista copiato troppo e viceversa.

(Ecco lo script se qualcuno è interessato o vuole sollevare problemi :)

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
Dal momento che stai confrontando, potrebbe essere utile includere un punto di riferimento. Queste cifre sono ancora accurate nel 2017 usando Python 3.6 con codice completamente compilato? Sto notando la risposta di seguito ( stackoverflow.com/a/17810305/26219 ) già pone domande a questa risposta.
Mark Edington,

4
usa il timeitmodulo. inoltre, non è possibile trarre grandi conclusioni da micro benchmark arbitrari come questo.
Corey Goldberg,

3
Se desideri includere una nuova opzione per 3.5+, [*old_list]dovrebbe essere approssimativamente equivalente a list(old_list), ma poiché si tratta della sintassi, non dei percorsi di chiamata delle funzioni generali, risparmierà un po 'in fase di esecuzione (e diversamente da old_list[:], che non digita convert, [*old_list]funziona su qualsiasi iterabile e produce a list).
ShadowRanger il

3
@CoreyGoldberg per un micro-benchmark leggermente meno arbitrario (usi timeit, corse di 50m invece di 100k) vedi stackoverflow.com/a/43220129/3745896
Fiume,

1
@ShadowRanger [*old_list]sembra sovraperformare quasi qualsiasi altro metodo. (vedi la mia risposta collegata nei commenti precedenti)
Fiume


126

Quali sono le opzioni per clonare o copiare un elenco in Python?

In Python 3, è possibile eseguire una copia superficiale con:

a_copy = a_list.copy()

In Python 2 e 3, puoi ottenere una copia superficiale con un'intera porzione dell'originale:

a_copy = a_list[:]

Spiegazione

Esistono due modi semantici per copiare un elenco. Una copia superficiale crea un nuovo elenco degli stessi oggetti, una copia profonda crea un nuovo elenco contenente nuovi oggetti equivalenti.

Copia elenco superficiale

Una copia superficiale copia solo l'elenco stesso, che è un contenitore di riferimenti agli oggetti nell'elenco. Se gli oggetti contenuti sono mutabili e ne viene modificato uno, la modifica verrà riflessa in entrambi gli elenchi.

Esistono diversi modi per farlo in Python 2 e 3. I modi Python 2 funzioneranno anche in Python 3.

Python 2

In Python 2, il modo idiomatico di creare una copia superficiale di un elenco è con una porzione completa dell'originale:

a_copy = a_list[:]

Puoi anche ottenere lo stesso risultato passando l'elenco attraverso il costruttore dell'elenco,

a_copy = list(a_list)

ma l'uso del costruttore è meno efficiente:

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

Python 3

In Python 3, gli elenchi ottengono il list.copymetodo:

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

Fare un altro puntatore non fa una copia

L'uso di new_list = my_list quindi modifica new_list ogni volta che my_list cambia. Perchè è questo?

my_listè solo un nome che punta all'elenco effettivo in memoria. Quando dici new_list = my_listche non stai facendo una copia, stai solo aggiungendo un altro nome che punta all'elenco originale in memoria. Possiamo avere problemi simili quando facciamo copie degli elenchi.

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

L'elenco è solo una matrice di puntatori al contenuto, quindi una copia superficiale copia solo i puntatori e quindi hai due elenchi diversi, ma hanno gli stessi contenuti. Per fare copie dei contenuti, è necessaria una copia profonda.

Copie profonde

Per creare una copia profonda di un elenco, in Python 2 o 3, utilizzare deepcopynel copymodulo :

import copy
a_deep_copy = copy.deepcopy(a_list)

Per dimostrare come ciò ci consente di creare nuovi elenchi secondari:

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

E così vediamo che l'elenco copiato in profondità è un elenco completamente diverso dall'originale. Potresti attivare la tua funzione, ma non farlo. È probabile che tu crei bug che altrimenti non avresti usando la funzione deepcopy della libreria standard.

Non usare eval

Potresti vederlo usato come un modo per deepcopy, ma non farlo:

problematic_deep_copy = eval(repr(a_list))
  1. È pericoloso, soprattutto se stai valutando qualcosa da una fonte di cui non ti fidi.
  2. Non è affidabile, se un sottoelemento che stai copiando non ha una rappresentazione che può essere valutata per riprodurre un elemento equivalente.
  3. È anche meno performante.

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

su 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
Non è necessario un deepcopy se l'elenco è 2D. Se si tratta di un elenco di elenchi e tali elenchi non contengono elenchi al loro interno, è possibile utilizzare un ciclo for. Attualmente sto usando list_copy=[] for item in list: list_copy.append(copy(item))ed è molto più veloce.
John Locke,

54

Ci sono già molte risposte che ti dicono come fare una copia corretta, ma nessuna di esse dice perché la tua "copia" originale non è riuscita.

Python non memorizza i valori in variabili; lega i nomi agli oggetti. Il tuo incarico originale ha preso l'oggetto a cui fa riferimento my_liste lo lega new_listanche. Indipendentemente dal nome che usi, c'è ancora un solo elenco, quindi le modifiche apportate quando ti riferisci ad esso come my_listpersisteranno quando si riferiscono ad esso come new_list. Ciascuna delle altre risposte a questa domanda offre diversi modi di creare un nuovo oggetto a cui legarsi new_list.

Ogni elemento di un elenco si comporta come un nome, in quanto ogni elemento si lega non esclusivamente a un oggetto. Una copia superficiale crea un nuovo elenco i cui elementi si legano agli stessi oggetti di prima.

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]

Per fare in modo che il tuo elenco copi un ulteriore passo, copia ogni oggetto a cui fa riferimento il tuo elenco e associa le copie degli elementi a un nuovo elenco.

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

Questa non è ancora una copia profonda, perché ogni elemento di un elenco può fare riferimento ad altri oggetti, proprio come l'elenco è associato ai suoi elementi. Per copiare in modo ricorsivo ogni elemento nell'elenco, quindi ogni altro oggetto a cui fa riferimento ciascun elemento e così via: eseguire una copia profonda.

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

Consultare la documentazione per ulteriori informazioni sui casi d'angolo durante la copia.


38

Uso thing[:]

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

35

Partiamo dall'inizio ed esploriamo questa domanda.

Supponiamo quindi di avere due elenchi:

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

E dobbiamo copiare entrambi gli elenchi, a partire dal primo elenco:

Quindi, prima proviamo impostando la variabile copyalla nostra lista originale, list_1:

copy=list_1

Ora, se stai pensando di copiare la copia dell'elenco_1, allora ti sbagli. La idfunzione può mostrarci se due variabili possono puntare allo stesso oggetto. Proviamo questo:

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

L'output è:

4329485320
4329485320

Entrambe le variabili sono esattamente lo stesso argomento. Sei sorpreso?

Quindi, come sappiamo, Python non memorizza nulla in una variabile, le variabili fanno semplicemente riferimento all'oggetto e l'oggetto memorizza il valore. Qui object è un listma abbiamo creato due riferimenti a quello stesso oggetto con due nomi di variabili differenti. Ciò significa che entrambe le variabili puntano allo stesso oggetto, solo con nomi diversi.

Quando lo fai copy=list_1, sta effettivamente facendo:

inserisci qui la descrizione dell'immagine

Qui nella lista di immagini_1 e copia ci sono due nomi di variabili ma l'oggetto è lo stesso per entrambe le variabili list

Quindi, se provi a modificare l'elenco copiato, modificherà anche l'elenco originale perché l'elenco è solo uno lì, modificherai tale elenco indipendentemente dall'elenco copiato o dall'elenco originale:

copy[0]="modify"

print(copy)
print(list_1)

produzione:

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

Quindi ha modificato l'elenco originale:

Passiamo ora a un metodo pythonic per la copia di liste.

copy_1=list_1[:]

Questo metodo risolve il primo problema riscontrato:

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

4338792136
4338791432

Così come possiamo vedere il nostro elenco di entrambi con ID diverso e ciò significa che entrambe le variabili puntano a oggetti diversi. Quindi quello che sta succedendo qui è:

inserisci qui la descrizione dell'immagine

Ora proviamo a modificare l'elenco e vediamo se affrontiamo ancora il problema precedente:

copy_1[0]="modify"

print(list_1)
print(copy_1)

L'output è:

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

Come puoi vedere, ha modificato solo l'elenco copiato. Ciò significa che ha funzionato.

Pensi che abbiamo finito? No. Proviamo a copiare la nostra lista nidificata.

copy_2=list_2[:]

list_2dovrebbe fare riferimento a un altro oggetto di cui è una copia list_2. Controlliamo:

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

Otteniamo l'output:

4330403592 4330403528

Ora possiamo supporre che entrambi gli elenchi puntino oggetti diversi, quindi ora proviamo a modificarlo e vediamo che sta dando ciò che vogliamo:

copy_2[0][1]="modify"

print(list_2,copy_2)

Questo ci dà l'output:

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

Questo può sembrare un po 'confuso, perché lo stesso metodo che abbiamo usato in precedenza ha funzionato. Proviamo a capirlo.

Quando lo fai:

copy_2=list_2[:]

Stai solo copiando l'elenco esterno, non l'elenco interno. Possiamo usare idancora una volta la funzione per verificarlo.

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

L'output è:

4329485832
4329485832

Quando lo facciamo copy_2=list_2[:], questo accade:

inserisci qui la descrizione dell'immagine

Crea la copia dell'elenco ma solo la copia dell'elenco esterno, non la copia dell'elenco nidificato, l'elenco nidificato è lo stesso per entrambe le variabili, quindi se si tenta di modificare l'elenco nidificato, verrà modificato anche l'elenco originale poiché l'oggetto elenco nidificato è lo stesso per entrambi gli elenchi.

Qual'è la soluzione? La soluzione è la deepcopyfunzione.

from copy import deepcopy
deep=deepcopy(list_2)

Controlliamo questo:

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

4322146056 4322148040

Entrambi gli elenchi esterni hanno ID diversi, proviamo questo sugli elenchi nidificati interni.

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

L'output è:

4322145992
4322145800

Come puoi vedere, entrambi gli ID sono diversi, il che significa che ora possiamo supporre che entrambi gli elenchi nidificati puntino a un oggetto diverso.

Ciò significa che quando fai deep=deepcopy(list_2)ciò che accade realmente:

inserisci qui la descrizione dell'immagine

Entrambi gli elenchi nidificati puntano oggetti diversi e ora hanno una copia separata dell'elenco nidificato.

Ora proviamo a modificare l'elenco nidificato e vediamo se ha risolto il problema precedente o meno:

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

Emette:

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

Come puoi vedere, non ha modificato l'elenco nidificato originale, ma ha modificato solo l'elenco copiato.


34

Il linguaggio di Python per farlo è newList = oldList[:]


34

Tempi di Python 3.6

Ecco i risultati di temporizzazione usando Python 3.6.8. Ricorda che questi tempi sono relativi l'uno all'altro, non assoluti.

Mi sono bloccato a fare solo copie superficiali e ho anche aggiunto alcuni nuovi metodi che non erano possibili in Python2, come list.copy()(l' equivalente di Python3 slice ) e due forme di decompressione dell'elenco ( *new_list, = liste 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

Possiamo vedere che il vincitore di Python2 fa ancora bene, ma non supera list.copy()di molto Python3 , soprattutto considerando la leggibilità superiore di quest'ultimo.

Il cavallo oscuro è il metodo di disimballaggio e reimballaggio ( b = [*a]), che è ~ 25% più veloce dell'affettatura grezza e più del doppio dell'altro metodo di disimballaggio ( *b, = a).

b = a * 1 anche sorprendentemente bene.

Si noti che questi metodi non producono risultati equivalenti per qualsiasi input diverso dagli elenchi. Funzionano tutti per oggetti affettabili, alcuni funzionano per qualsiasi iterabile, ma copy.copy()funzionano solo per oggetti Python più generali.


Ecco il codice di test per le parti interessate ( modello da qui ):

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
Può confermare ancora una storia simile su 3.8 b=[*a]- l'unico modo ovvio per farlo;).
SuperShoot

20

Tutti gli altri contributori hanno dato ottime risposte, che funzionano quando si dispone di un elenco a singola dimensione (livellato), tuttavia dei metodi menzionati finora, copy.deepcopy()funziona solo per clonare / copiare un elenco e non farlo puntare agli listoggetti nidificati quando si è lavorare con liste nidificate multidimensionali (lista di liste). Mentre Felix Kling si riferisce a questo nella sua risposta, c'è un po 'di più sul problema e probabilmente una soluzione alternativa che utilizza gli incorporati che potrebbero rivelarsi un'alternativa più veloce a deepcopy.

Mentre new_list = old_list[:], copy.copy(old_list)'e per Py3k old_list.copy()funzionano per elenchi a livello singolo, tornano a indicare gli listoggetti nidificati all'interno di old_liste il new_list, e le modifiche a uno degli listoggetti vengono perpetuate nell'altro.

Modifica: nuove informazioni portate alla luce

Come sottolineato sia da Aaron Hall che dal PM 2Ring l' utilizzo eval()non è solo una cattiva idea, è anche molto più lento di copy.deepcopy().

Ciò significa che per gli elenchi multidimensionali, l'unica opzione è copy.deepcopy(). Detto questo, in realtà non è un'opzione poiché le prestazioni vanno molto a sud quando si tenta di utilizzarlo su un array multidimensionale di dimensioni moderate. Ho provato a timeitutilizzare un array 42x42, non inaudito o addirittura così grande per le applicazioni bioinformatiche, e ho rinunciato ad aspettare una risposta e ho appena iniziato a digitare la mia modifica per questo post.

Sembrerebbe che l'unica vera opzione sia quella di inizializzare più elenchi e lavorarci su indipendentemente. Se qualcuno ha altri suggerimenti, su come gestire la copia di elenchi multidimensionali, sarebbe apprezzato.

Come altri hanno già affermato, ci sono problemi di prestazioni significativi utilizzando il copymodulo e copy.deepcopy per gli elenchi multidimensionali .


5
Ciò non funzionerà sempre, poiché non esiste alcuna garanzia che la stringa restituita da repr()sia sufficiente per ricreare l'oggetto. Inoltre, eval()è uno strumento di ultima istanza; vedi Eval è davvero pericoloso dal veterano SO Ned Batchelder per i dettagli. Quindi, quando sostenete l'uso, eval()dovreste davvero dire che può essere pericoloso.
PM 2Ring

1
Punto valido. Anche se penso che il punto di Batchelder sia che avere la eval()funzione in Python in generale sia un rischio. Non è tanto se si utilizza o meno la funzione nel codice, ma è un buco di sicurezza in Python in sé e per sé. Il mio esempio non utilizza con una funzione che riceve input da input(), sys.agrvo anche un file di testo. È più sulla falsariga di inizializzare un elenco multidimensionale vuoto una volta, e quindi avere solo un modo di copiarlo in un ciclo invece di reinizializzare ad ogni iterazione del ciclo.
AMR,

1
Come ha sottolineato @AaronHall, è probabile che ci sia un notevole problema di prestazioni nell'uso new_list = eval(repr(old_list)), quindi oltre ad essere una cattiva idea, probabilmente è anche troppo lento per funzionare.
AMR,

13

Mi sorprende che non sia stato ancora menzionato, quindi per completezza ...

È possibile eseguire il disimballaggio dell'elenco con "l'operatore splat": *che copia anche gli elementi dell'elenco.

old_list = [1, 2, 3]

new_list = [*old_list]

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

L'ovvio aspetto negativo di questo metodo è che è disponibile solo in Python 3.5+.

Per quanto riguarda i tempi, questo sembra funzionare meglio di altri metodi comuni.

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
Come si comporta questo metodo quando si modificano le copie?
not2qubit,

2
@ not2qubit intendi aggiungere o modificare elementi del nuovo elenco. Nell'esempio old_liste new_listsono due elenchi diversi, la modifica di uno non cambierà l'altro (a meno che non si stia mutando direttamente gli elementi stessi (come l'elenco di elenchi), nessuno di questi metodi è copie profonde).
SCB

8

Un approccio molto semplice indipendente dalla versione di Python mancava nelle risposte già fornite che puoi usare la maggior parte del tempo (almeno lo faccio):

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

Tuttavia, se my_list contiene altri contenitori (ad esempio elenchi nidificati) è necessario utilizzare deepcopy come altri suggeriti nelle risposte sopra dalla libreria di copie. Per esempio:

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

. Bonus : se non vuoi copiare elementi usa (aka copia superficiale):

new_list = my_list[:]

Comprendiamo la differenza tra la Soluzione n. 1 e la Soluzione n. 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])

Come puoi vedere, la soluzione n. 1 ha funzionato perfettamente quando non stavamo usando gli elenchi nidificati. Controlliamo cosa accadrà quando applicheremo la soluzione n. 1 agli elenchi nidificati.

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

Nota che ci sono alcuni casi in cui se hai definito la tua classe personalizzata e vuoi mantenere gli attributi, dovresti usare copy.copy()o copy.deepcopy()piuttosto che le alternative, ad esempio in 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))

Uscite:

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 Cerca di capirlo. Diciamo che my_list è nella memoria heap nella posizione X, ovvero my_list punta alla X. Ora assegnando new_list = my_liststai lasciando che New_list punta alla X. Questo è noto come copia superficiale.

Ora, se assegni, new_list = my_list[:]stai semplicemente copiando ogni oggetto di my_list in new_list. Questo è noto come Deep copia.

L'altro modo per farlo è:

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

3

Volevo pubblicare qualcosa di leggermente diverso rispetto ad alcune delle altre risposte. Anche se questa probabilmente non è l'opzione più comprensibile o più veloce, fornisce una visione interna di come funziona la copia profonda, oltre ad essere un'altra opzione alternativa per la copia profonda. Non importa se la mia funzione ha dei bug, poiché il punto è quello di mostrare un modo per copiare oggetti come la risposta alla domanda, ma anche di usarlo come punto per spiegare come funziona la deepcopy al suo interno.

Al centro di qualsiasi funzione di copia profonda è il modo di fare una copia superficiale. Come? Semplice. Qualsiasi funzione di copia profonda duplica solo i contenitori di oggetti immutabili. Quando si copia in profondità un elenco nidificato, si duplicano solo gli elenchi esterni, non gli oggetti mutabili all'interno degli elenchi. Stai solo duplicando i contenitori. Lo stesso vale anche per le lezioni. Quando si esegue una copia in profondità di una classe, si esegue la copia in profondità di tutti i suoi attributi mutabili. Così come? Come mai devi solo copiare i contenitori, come liste, dadi, tuple, iter, classi e istanze di classe?

È semplice. Un oggetto mutabile non può davvero essere duplicato. Non può mai essere modificato, quindi è solo un singolo valore. Ciò significa che non devi mai duplicare stringhe, numeri, bool o nessuno di questi. Ma come duplicheresti i contenitori? Semplice. Devi solo inizializzare un nuovo contenitore con tutti i valori. Deepcopy si basa sulla ricorsione. Duplica tutti i contenitori, anche quelli con contenitori all'interno, fino a quando non rimangono più contenitori. Un contenitore è un oggetto immutabile.

Una volta che lo sai, duplicare completamente un oggetto senza riferimenti è abbastanza facile. Ecco una funzione per la copia in profondità dei tipi di dati di base (non funzionerebbe per le classi personalizzate ma potresti sempre aggiungerla)

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

La deepcopy integrata di Python si basa su questo esempio. L'unica differenza è che supporta altri tipi e supporta anche le classi utente duplicando gli attributi in una nuova classe duplicata e blocca anche la ricorsione infinita con un riferimento a un oggetto che è già stato visto utilizzando un elenco di memo o un dizionario. E questo è tutto per fare copie profonde. Fondamentalmente, fare una copia profonda è solo fare copie superficiali. Spero che questa risposta aggiunga qualcosa alla domanda.

ESEMPI

Supponi di avere questo elenco: [1, 2, 3] . I numeri immutabili non possono essere duplicati, ma è possibile l'altro livello. Puoi duplicarlo usando una comprensione dell'elenco: [x per x in [1, 2, 3]

Ora, immagina di avere questo elenco: [[1, 2], [3, 4], [5, 6]] . Questa volta, si desidera creare una funzione, che utilizza la ricorsione per copiare in profondità tutti i livelli dell'elenco. Invece della precedente comprensione dell'elenco:

[x for x in _list]

Ne utilizza uno nuovo per gli elenchi:

[deepcopy_list(x) for x in _list]

E deepcopy_list si presenta così:

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

Quindi ora hai una funzione che può deepcopiare qualsiasi elenco di str, bool, floast, in e persino liste su infiniti livelli usando la ricorsione. E il gioco è fatto, deepcopying.

TLDR : Deepcopy utilizza la ricorsione per duplicare oggetti e restituisce semplicemente gli stessi oggetti immutabili di prima, poiché gli oggetti immutabili non possono essere duplicati. Tuttavia, copia in profondità gli strati più interni di oggetti mutabili fino a raggiungere lo strato mutabile più esterno di un oggetto.


3

Una leggera prospettiva pratica per guardare nella memoria attraverso id e 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

Ricordalo in Python quando lo fai:

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

List2 non memorizza l'elenco effettivo, ma un riferimento a list1. Quindi, quando fai qualsiasi cosa in list1, anche list2 cambia. utilizzare il modulo copia (non predefinito, scarica su pip) per creare una copia originale dell'elenco ( copy.copy()per elenchi semplici, copy.deepcopy()per quelli nidificati). Questo crea una copia che non cambia con il primo elenco.


1

L'opzione deepcopy è l'unico metodo che funziona per me:

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

porta alla produzione di:

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

Questo perché, la riga new_list = my_listassegna un nuovo riferimento alla variabile my_listche new_list è simile al Ccodice indicato di seguito,

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

Utilizzare il modulo copia per creare un nuovo elenco in base a

import copy
new_list = copy.deepcopy(my_list)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.