Domanda Come clonare o copiare un elenco?


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

utilizzando new_list = my_list quindi modifica new_list ogni volta my_list i cambiamenti.
Perchè è questo?


1690
2018-04-10 08:49


origine


risposte:


Con new_list = my_list, in realtà non hai due elenchi. L'incarico copia semplicemente il riferimento alla lista, non la lista attuale, quindi entrambi new_list e my_list fare riferimento alla stessa lista dopo l'assegnazione.

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

  • Puoi usare l'integrato list.copy() metodo (disponibile da python 3.3):

    new_list = old_list.copy()
    
  • Puoi tagliarlo:

    new_list = old_list[:]
    

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

  • Puoi usare il built-in list() funzione:

    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 scoprire il tipo di dati di old_list primo.

  • Se la lista contiene oggetti e vuoi copiarli, usa generico copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)
    

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

Esempio:

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

Risultato:

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 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) - puro pitone Copy() metodo che copia le classi con deepcopy
  3. 1.488 sec (14.88us / itn) - puro pitone Copy() metodo non copia le classi (solo dicts / liste / tuple)
  4. 0,325 sec (3,25 us / itn) - for item in old_list: new_list.append(item)
  5. 0,217 sec (2,17us / itn) - [i for i in old_list] (un lista di comprensione)
  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[:] (lista affettare)

Quindi il più veloce è l'affettamento delle liste. Ma attenzione copy.copy(), list[:] e list(list), a differenza di copy.deepcopy() e la versione python non copia liste, dizionari e istanze di classe nella lista, quindi se gli originali cambiano, cambieranno anche nella lista copiata e viceversa.

(Ecco lo script se qualcuno è interessato o vuole sollevare eventuali 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

MODIFICARE: Aggiunti classi e datt nuovi stile vecchio stile ai benchmark, e reso la versione python molto più veloce e ha aggiunto altri metodi tra cui espressioni di lista e extend().


447
2018-04-10 10:16



Io ho stato detto quel Python 3.3+ aggiunge list.copy() metodo, che dovrebbe essere veloce quanto affettare:

newlist = old_list.copy()


116
2017-07-23 12:32



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

In Python 3, una copia superficiale può essere fatta con:

a_copy = a_list.copy()

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

a_copy = a_list[:]

Spiegazione

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

Copia della lista ridotta

Una copia superficiale copia solo la lista stessa, che è un contenitore di riferimenti agli oggetti nella lista. Se gli oggetti contenuti sono mutabili e uno viene modificato, la modifica verrà riflessa in entrambe le liste.

Ci sono 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 una lista è con una porzione completa dell'originale:

a_copy = a_list[:]

Puoi anche ottenere lo stesso risultato passando la lista attraverso il costruttore della lista,

a_copy = list(a_list)

ma usare il 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, le liste ottengono il list.copy metodo:

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 fa non fare una copia

Usando new_list = my_list modifica quindi new_list ogni volta che my_list cambia. Perchè è questo?

my_list è solo un nome che punta alla lista attuale in memoria. Quando dici new_list = my_list non stai facendo una copia, stai solo aggiungendo un altro nome che punta a quell'elenco originale nella memoria. Possiamo avere problemi simili quando facciamo copie di elenchi.

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

La lista è solo una serie di puntatori ai contenuti, quindi una copia superficiale copia solo i puntatori e quindi hai due elenchi diversi, ma hanno lo stesso contenuto. Per fare copie dei contenuti, è necessaria una copia profonda.

Copie profonde

Fare un copia profonda di una lista, in Python 2 o 3, usa deepcopy nel copy modulo:

import copy
a_deep_copy = copy.deepcopy(a_list)

Per dimostrare come questo ci permette di creare nuovi sotto-elenchi:

>>> 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 eseguire la tua funzione, ma non farlo. È probabile che crei bug che altrimenti non avresti utilizzando la funzione deepcopy della libreria standard.

Non usare eval

Potresti vedere questo usato come un modo per eseguire la 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 Python 64 bit 2.7:

>>> import timeit
>>> import copy
>>> l = range(10)
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
27.55826997756958
>>> min(timeit.repeat(lambda: eval(repr(l))))
29.04534101486206

su Python 3.5 a 64 bit:

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



Ci sono già molte risposte che ti dicono come fare una copia corretta, ma nessuno di loro dice perché la tua "copia" originale è fallita.

Python non memorizza valori nelle variabili; lega i nomi agli oggetti. Il tuo incarico originale ha preso l'oggetto a cui si riferisce my_list e legato a new_list anche. Indipendentemente dal nome che usi, c'è ancora una sola lista, quindi le modifiche fatte quando ci si riferisce ad essa come my_list persisterà quando ci si riferisce ad esso come new_list. Ognuna delle altre risposte a questa domanda ti offre diversi modi per creare un nuovo oggetto a cui legarti new_list.

Ogni elemento di una lista si comporta come un nome, in quanto ogni elemento si lega non esclusivamente ad un oggetto. Una copia superficiale crea una nuova lista 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 la tua lista copi un ulteriore passo, copia ogni oggetto a cui si riferisce la tua lista e associa quelle copie di elementi a una nuova lista.

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 una lista può riferirsi ad altri oggetti, proprio come la lista è legata ai suoi elementi. Per copiare ricorsivamente ogni elemento dell'elenco e 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)

Vedere la documentazione per ulteriori informazioni sui casi d'angolo nella copia.


42
2017-11-23 16:45



new_list = list(old_list)


30
2018-04-10 09:03



Uso thing[:]

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

27
2018-04-10 08:53