¿Cómo clonar o copiar una lista?

2551

¿Cuáles son las opciones para clonar o copiar una lista en Python?

Durante el uso new_list = my_list, cualquier modificación a los new_listcambios my_listcada vez. ¿Por qué es esto?

aF.
fuente

Respuestas:

3331

Con new_list = my_list, en realidad no tienes dos listas. La asignación solo copia la referencia a la lista, no la lista real, por lo que ambas new_listy se my_listrefieren a la misma lista después de la asignación.

Para copiar realmente la lista, tiene varias posibilidades:

  • Puede usar el list.copy()método incorporado (disponible desde Python 3.3):

    new_list = old_list.copy()
  • Puedes cortarlo:

    new_list = old_list[:]

    La opinión de Alex Martelli (al menos en 2007 ) sobre esto es que es una sintaxis extraña y no tiene sentido usarla nunca . ;) (En su opinión, el siguiente es más legible).

  • Puede usar la list()función integrada:

    new_list = list(old_list)
  • Puedes usar genérico copy.copy():

    import copy
    new_list = copy.copy(old_list)
    

    Esto es un poco más lento que list()porque tiene que averiguar old_listprimero el tipo de datos .

  • Si la lista contiene objetos y desea copiarlos también, use genérico copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)
    

    Obviamente, el método más lento y que necesita más memoria, pero a veces inevitable.

Ejemplo:

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

Resultado:

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
fuente
77
Si no me estoy equivocando: newlist = [*mylist]también es una posibilidad en Python 3. newlist = list(mylist)aunque quizás sea más claro.
Stéphane
99
otra posibilidad es new_list = old_list * 1
aris
44
¿Cuáles de estos métodos son de copia superficial y cuáles de ellos son de copia profunda?
Eswar
44
@Eswar: todos menos el último hacen una copia superficial
Felix Kling
3
@Eswar es una copia superficial.
juanpa.arrivillaga
604

Felix ya proporcionó una excelente respuesta, pero pensé que haría una comparación de velocidad de los diversos métodos:

  1. 10.59 segundos (105.9us / itn) - copy.deepcopy(old_list)
  2. 10.16 sec (101.6us / itn) - Copy()método de python puro copiando clases con deepcopy
  3. 1.488 segundos (14.88us / itn): el Copy()método Python puro no copia clases (solo dictados / listas / tuplas)
  4. 0.325 segundos (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 lista de comprensión )
  6. 0.186 segundos (1.86us / itn) - copy.copy(old_list)
  7. 0.075 segundos (0.75us / itn) - list(old_list)
  8. 0.053 segundos (0.53us / itn) - new_list = []; new_list.extend(old_list)
  9. 0.039 sec (0.39us / itn) - old_list[:]( corte de lista )

Entonces, el más rápido es el corte de listas. Pero tenga en cuenta que copy.copy(), list[:]y list(list), a diferencia de copy.deepcopy()la versión de Python, no copie ninguna lista, diccionario o instancia de clase en la lista, por lo que si los originales cambian, también cambiarán en la lista copiada y viceversa.

(Aquí está el script si alguien está interesado o quiere plantear algún problema :)

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
crio
fuente
99
Dado que está realizando una evaluación comparativa, puede ser útil incluir un punto de referencia. ¿Estas cifras siguen siendo precisas en 2017 usando Python 3.6 con código completamente compilado? Estoy notando que la respuesta a continuación ( stackoverflow.com/a/17810305/26219 ) ya cuestiona esta respuesta.
Mark Edington
44
usa el timeitmódulo. Además, no se puede concluir mucho de micro puntos de referencia arbitrarios como este.
Corey Goldberg
3
Si desea incluir una nueva opción para 3.5+, [*old_list]debería ser más o menos equivalente a list(old_list), pero dado que es una sintaxis, no rutas de llamadas de función generales, ahorrará un poco en tiempo de ejecución (y old_list[:], a diferencia , que no escribe convert, [*old_list]funciona en cualquier iterable y produce a list).
ShadowRanger
3
@CoreyGoldberg para un micro-punto de referencia un poco menos arbitrario (usos timeit, 50m de carreras en lugar de 100k) ver stackoverflow.com/a/43220129/3745896
River
1
@ShadowRanger en [*old_list]realidad parece superar a casi cualquier otro método. (ver mi respuesta vinculada en comentarios anteriores)
River
126

¿Cuáles son las opciones para clonar o copiar una lista en Python?

En Python 3, se puede hacer una copia superficial con:

a_copy = a_list.copy()

En Python 2 y 3, puede obtener una copia superficial con una porción completa del original:

a_copy = a_list[:]

Explicación

Hay dos formas semánticas para copiar una lista. Una copia superficial crea una nueva lista de los mismos objetos, una copia profunda crea una nueva lista que contiene nuevos objetos equivalentes.

Copia de lista superficial

Una copia superficial solo copia la lista misma, que es un contenedor de referencias a los objetos en la lista. Si los objetos contenidos en sí mismos son mutables y uno se modifica, el cambio se reflejará en ambas listas.

Hay diferentes formas de hacer esto en Python 2 y 3. Las formas de Python 2 también funcionarán en Python 3.

Python 2

En Python 2, la forma idiomática de hacer una copia superficial de una lista es con una porción completa del original:

a_copy = a_list[:]

También puede lograr lo mismo pasando la lista a través del constructor de la lista,

a_copy = list(a_list)

pero usar el constructor es menos eficiente:

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

Python 3

En Python 3, las listas obtienen el list.copymétodo:

a_copy = a_list.copy()

En 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

Hacer otro puntero no hace una copia

Usando new_list = my_list luego modifica new_list cada vez que my_list cambia. ¿Por qué es esto?

my_listes solo un nombre que apunta a la lista real en la memoria. Cuando dice new_list = my_listque no está haciendo una copia, simplemente está agregando otro nombre que apunta a esa lista original en la memoria. Podemos tener problemas similares cuando hacemos copias de las listas.

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

La lista es solo una matriz de punteros a los contenidos, por lo que una copia superficial simplemente copia los punteros, por lo que tiene dos listas diferentes, pero tienen el mismo contenido. Para hacer copias de los contenidos, necesita una copia profunda.

Copias profundas

Para hacer una copia profunda de una lista, en Python 2 o 3, use deepcopyen el copymódulo :

import copy
a_deep_copy = copy.deepcopy(a_list)

Para demostrar cómo esto nos permite hacer nuevas sublistas:

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

Y así vemos que la lista copiada en profundidad es una lista completamente diferente de la original. Podrías rodar tu propia función, pero no lo hagas. Es probable que cree errores que de otro modo no tendría utilizando la función de copia profunda de la biblioteca estándar.

No usar eval

Puede ver esto usado como una forma de hacer una copia profunda, pero no lo haga:

problematic_deep_copy = eval(repr(a_list))
  1. Es peligroso, especialmente si está evaluando algo de una fuente en la que no confía.
  2. No es confiable, si un subelemento que está copiando no tiene una representación que pueda evaluarse para reproducir un elemento equivalente.
  3. También es menos eficiente.

En Python 2.7 de 64 bits:

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

en Python 3.5 de 64 bits:

>>> 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
fuente
1
No necesita una copia profunda si la lista es 2D. Si es una lista de listas, y esas listas no tienen listas dentro de ellas, puede usar un bucle for. Actualmente, estoy usando list_copy=[] for item in list: list_copy.append(copy(item))y es mucho más rápido.
John Locke el
54

Ya hay muchas respuestas que le dicen cómo hacer una copia adecuada, pero ninguna de ellas dice por qué falló su 'copia' original.

Python no almacena valores en variables; une nombres a objetos. Su asignación original tomó el objeto al que hace referencia my_listy también lo vinculó new_list. Independientemente del nombre que utilice, todavía hay una sola lista, por lo que los cambios realizados al referirse a él my_listpersistirán al referirse a él como new_list. Cada una de las otras respuestas a esta pregunta le brinda diferentes formas de crear un nuevo objeto al que vincularse new_list.

Cada elemento de una lista actúa como un nombre, ya que cada elemento se une no exclusivamente a un objeto. Una copia superficial crea una nueva lista cuyos elementos se unen a los mismos objetos que antes.

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]

Para llevar su copia de la lista un paso más allá, copie cada objeto al que hace referencia su lista y vincule esas copias de elementos a una nueva lista.

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

Esto aún no es una copia profunda, porque cada elemento de una lista puede referirse a otros objetos, al igual que la lista está vinculada a sus elementos. Para copiar recursivamente cada elemento de la lista, y luego cada uno de los objetos mencionados por cada elemento, y así sucesivamente: realice una copia profunda.

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

Consulte la documentación para obtener más información sobre casos de esquina en la copia.

Jack
fuente
38

Utilizar thing[:]

>>> a = [1,2]
>>> b = a[:]
>>> a += [3]
>>> a
[1, 2, 3]
>>> b
[1, 2]
>>> 
Paul Tarjan
fuente
35

Comencemos desde el principio y exploremos esta pregunta.

Supongamos que tiene dos listas:

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

Y tenemos que copiar ambas listas, ahora comenzando desde la primera lista:

Entonces, primero intentemos estableciendo la variable copyen nuestra lista original list_1:

copy=list_1

Ahora, si está pensando que la copia copió la lista_1, entonces está equivocado. La idfunción puede mostrarnos si dos variables pueden apuntar al mismo objeto. Intentemos esto:

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

El resultado es:

4329485320
4329485320

Ambas variables son exactamente el mismo argumento. ¿Estás sorprendido?

Entonces, como sabemos, python no almacena nada en una variable, las variables solo hacen referencia al objeto y el objeto almacena el valor. Aquí el objeto es un listpero creamos dos referencias a ese mismo objeto por dos nombres de variables diferentes. Esto significa que ambas variables apuntan al mismo objeto, solo que con diferentes nombres.

Cuando lo haces copy=list_1, en realidad está haciendo:

ingrese la descripción de la imagen aquí

Aquí en la lista de imágenes_1 y la copia hay dos nombres de variables, pero el objeto es el mismo para ambas variables, que es list

Entonces, si intenta modificar la lista copiada, también modificará la lista original porque la lista es solo una allí, la modificará sin importar lo que haga desde la lista copiada o desde la lista original:

copy[0]="modify"

print(copy)
print(list_1)

salida:

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

Entonces modificó la lista original:

Ahora pasemos a un método pitónico para copiar listas.

copy_1=list_1[:]

Este método soluciona el primer problema que tuvimos:

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

4338792136
4338791432

Entonces, como podemos ver, nuestra lista tiene una identificación diferente y significa que ambas variables están apuntando a objetos diferentes. Entonces, lo que realmente está sucediendo aquí es:

ingrese la descripción de la imagen aquí

Ahora intentemos modificar la lista y veamos si aún enfrentamos el problema anterior:

copy_1[0]="modify"

print(list_1)
print(copy_1)

El resultado es:

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

Como puede ver, solo modificó la lista copiada. Eso significa que funcionó.

¿Crees que hemos terminado? No. Intentemos copiar nuestra lista anidada.

copy_2=list_2[:]

list_2debe hacer referencia a otro objeto que es copia de list_2. Vamos a revisar:

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

Obtenemos la salida:

4330403592 4330403528

Ahora podemos suponer que ambas listas apuntan a un objeto diferente, así que ahora tratemos de modificarlo y veamos que está dando lo que queremos:

copy_2[0][1]="modify"

print(list_2,copy_2)

Esto nos da la salida:

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

Esto puede parecer un poco confuso, porque el mismo método que utilizamos anteriormente funcionó. Tratemos de entender esto.

Cuando tu lo hagas:

copy_2=list_2[:]

Solo está copiando la lista externa, no la lista interna. Podemos usar la idfunción una vez más para verificar esto.

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

El resultado es:

4329485832
4329485832

Cuando lo hacemos copy_2=list_2[:], esto sucede:

ingrese la descripción de la imagen aquí

Crea la copia de la lista, pero solo la copia de la lista externa, no la copia de la lista anidada, la lista anidada es la misma para ambas variables, por lo que si intenta modificar la lista anidada, también modificará la lista original ya que el objeto de la lista anidada es el mismo para ambas listas

¿Cuál es la solución? La solución es la deepcopyfunción.

from copy import deepcopy
deep=deepcopy(list_2)

Vamos a ver esto:

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

4322146056 4322148040

Ambas listas externas tienen ID diferentes, intentemos esto en las listas anidadas internas.

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

El resultado es:

4322145992
4322145800

Como puede ver, ambas ID son diferentes, lo que significa que podemos suponer que ambas listas anidadas apuntan a un objeto diferente ahora.

Esto significa que cuando haces deep=deepcopy(list_2)lo que realmente sucede:

ingrese la descripción de la imagen aquí

Ambas listas anidadas apuntan a un objeto diferente y ahora tienen una copia separada de la lista anidada.

Ahora intentemos modificar la lista anidada y ver si resolvió el problema anterior o no:

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

Produce:

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

Como puede ver, no modificó la lista anidada original, solo modificó la lista copiada.

Aaditya Ura
fuente
34

El idioma de Python para hacer esto es newList = oldList[:]

erisco
fuente
34

Python 3.6 Tiempos

Aquí están los resultados de sincronización usando Python 3.6.8. Tenga en cuenta que estos tiempos son relativos entre sí, no absolutos.

Me limité a hacer solo copias superficiales, y también agregué algunos métodos nuevos que no eran posibles en Python2, como list.copy()(el equivalente de corte de Python3 ) y dos formas de desempaquetar listas ( *new_list, = listy 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

Podemos ver que el ganador de Python2 todavía lo hace bien, pero no supera list.copy()mucho a Python3 , especialmente teniendo en cuenta la legibilidad superior de este último.

El caballo oscuro es el método de desempaque y reempaque ( b = [*a]), que es ~ 25% más rápido que el corte en bruto, y más del doble de rápido que el otro método de desempaque ( *b, = a).

b = a * 1 También lo hace sorprendentemente bien.

Tenga en cuenta que estos métodos no generan resultados equivalentes para ninguna entrada que no sean listas. Todos funcionan para objetos divisibles, algunos funcionan para cualquier iterable, pero solo copy.copy()funcionan para objetos Python más generales.


Aquí está el código de prueba para las partes interesadas ( Plantilla de aquí ):

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))
Río
fuente
1
Puede confirmar una historia similar en 3.8 b=[*a], la única forma obvia de hacerlo;).
SuperShoot
20

Todos los demás contribuyentes dieron excelentes respuestas, que funcionan cuando tiene una lista de una sola dimensión (nivelada), sin embargo, de los métodos mencionados hasta ahora, solo copy.deepcopy()funciona para clonar / copiar una lista y no hacer que apunte a los listobjetos anidados cuando está trabajando con listas anidadas multidimensionales (lista de listas). Si bien Felix Kling se refiere a eso en su respuesta, hay un poco más sobre el problema y posiblemente una solución alternativa con el uso de elementos integrados que podrían ser una alternativa más rápida deepcopy.

Mientras new_list = old_list[:], copy.copy(old_list)'y para Py3k old_list.copy()funcionan para listas de un solo nivel, vuelven a apuntar a los listobjetos anidados dentro de old_listy new_list, y los cambios en uno de los listobjetos se perpetúan en el otro.

Editar: Nueva información sacada a la luz

Como lo señalaron Aaron Hall y PM 2Ring, el uso eval()no solo es una mala idea, sino que también es mucho más lento copy.deepcopy().

Esto significa que para las listas multidimensionales, la única opción es copy.deepcopy(). Dicho esto, realmente no es una opción, ya que el rendimiento va al sur cuando intentas usarlo en una matriz multidimensional de tamaño moderado. Traté de timeitusar una matriz de 42x42, no tan desconocida o incluso tan grande para las aplicaciones de bioinformática, y dejé de esperar una respuesta y comencé a escribir mi edición en esta publicación.

Parece que la única opción real es inicializar múltiples listas y trabajar en ellas de forma independiente. Si alguien tiene alguna otra sugerencia sobre cómo manejar la copia de listas multidimensionales, se agradecería.

Como han dicho otros, existen problemas importantes de rendimiento al usar el copymódulo y copy.deepcopy para las listas multidimensionales .

AMR
fuente
55
Esto no siempre funcionará, ya que no hay garantía de que la cadena devuelta repr()sea ​​suficiente para volver a crear el objeto. Además, eval()es una herramienta de último recurso; ver Eval realmente es peligroso por el veterano de SO Ned Batchelder para más detalles. Entonces, cuando defiende el uso eval(), realmente debe mencionar que puede ser peligroso.
PM 2Ring
1
Punto justo. Aunque creo que el punto de Batchelder es que tener la eval()función en Python en general es un riesgo. No es tanto si hace uso de la función en el código o no, sino que es un agujero de seguridad en Python en sí mismo. Mi ejemplo no lo está utilizando con una función que recibe información de input(), sys.agrvo incluso un archivo de texto. Es más similar a la inicialización de una lista multidimensional en blanco una vez, y luego simplemente tener una forma de copiarla en un bucle en lugar de reinicializar en cada iteración del bucle.
AMR
1
Como señaló @AaronHall, es probable que el uso de un problema de rendimiento sea significativo new_list = eval(repr(old_list)), por lo que, además de ser una mala idea, probablemente también sea demasiado lento para funcionar.
AMR
13

Me sorprende que esto no se haya mencionado aún, así que en aras de la integridad ...

Puede realizar el desempaquetado de la lista con el "operador splat": *que también copiará elementos de su lista.

old_list = [1, 2, 3]

new_list = [*old_list]

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

La desventaja obvia de este método es que solo está disponible en Python 3.5+.

Sin embargo, esto parece funcionar mejor que otros métodos comunes.

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
fuente
1
¿Cómo se comporta este método al modificar copias?
not2qubit
2
@ not2qubit te refieres a agregar o editar elementos de la nueva lista. En el ejemplo old_listy new_listhay dos listas diferentes, editar una no cambiará la otra (a menos que esté mutando directamente los elementos mismos (como la lista de la lista), ninguno de estos métodos son copias profundas).
SCB
8

Faltaba un enfoque muy simple independiente de la versión de Python en las respuestas ya dadas que puede usar la mayoría de las veces (al menos lo hago):

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

Sin embargo, si my_list contiene otros contenedores (por ejemplo, listas anidadas) debe usar la copia profunda como otros sugirieron en las respuestas anteriores de la biblioteca de copias. Por ejemplo:

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

. Bonificación : si no desea copiar elementos, use (también conocido como copia superficial):

new_list = my_list[:]

Comprendamos la diferencia entre la Solución n. ° 1 y la Solución 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])

Como puede ver, la Solución # 1 funcionó perfectamente cuando no estábamos usando las listas anidadas. Veamos qué sucederá cuando apliquemos la solución n. ° 1 a las listas anidadas.

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

Tenga en cuenta que hay algunos casos en los que si ha definido su propia clase personalizada y desea conservar los atributos, debería usar copy.copy()o copy.deepcopy()no las alternativas, por ejemplo, en 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))

Salidas:

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
fuente
5
new_list = my_list[:]

new_list = my_list Intenta entender esto. Digamos que my_list está en la memoria de almacenamiento dinámico en la ubicación X, es decir, my_list apunta a la X. Ahora asignandonew_list = my_list que estás dejando que new_list apunte a la X. Esto se conoce como Copia superficial.

Ahora, si lo asigna, new_list = my_list[:]simplemente está copiando cada objeto de my_list en new_list. Esto se conoce como copia profunda.

La otra forma en que puede hacer esto es:

  • new_list = list(old_list)
  • import copy new_list = copy.deepcopy(old_list)
Ravi Shankar
fuente
3

Quería publicar algo un poco diferente a algunas de las otras respuestas. Aunque es probable que esta no sea la opción más comprensible o más rápida, proporciona una visión interna de cómo funciona la copia profunda, además de ser otra opción alternativa para la copia profunda. Realmente no importa si mi función tiene errores, ya que el objetivo es mostrar una forma de copiar objetos como las respuestas a las preguntas, pero también usar esto como un punto para explicar cómo funciona la copia profunda en su núcleo.

El núcleo de cualquier función de copia profunda es la forma de hacer una copia superficial. ¿Cómo? Simple. Cualquier función de copia profunda solo duplica los contenedores de objetos inmutables. Cuando copia en profundidad una lista anidada, solo está duplicando las listas externas, no los objetos mutables dentro de las listas. Solo estás duplicando los contenedores. Lo mismo funciona para las clases, también. Cuando copia en profundidad una clase, copia en profundidad todos sus atributos mutables. ¿Así que cómo? ¿Cómo es que solo tiene que copiar los contenedores, como listas, dictados, tuplas, iters, clases e instancias de clase?

Es simple. Un objeto mutable no se puede duplicar realmente. Nunca se puede cambiar, por lo que es solo un valor único. Eso significa que nunca tendrá que duplicar cadenas, números, bools o ninguno de esos. ¿Pero cómo duplicaría los contenedores? Simple. Solo debe inicializar un nuevo contenedor con todos los valores. Deepcopy se basa en la recursividad. Duplica todos los contenedores, incluso aquellos con contenedores dentro de ellos, hasta que no queden contenedores. Un contenedor es un objeto inmutable.

Una vez que sepa eso, duplicar completamente un objeto sin ninguna referencia es bastante fácil. Aquí hay una función para realizar una copia en profundidad de los tipos de datos básicos (no funcionaría para clases personalizadas, pero siempre puede agregar eso)

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 copia profunda integrada de Python se basa en ese ejemplo. La única diferencia es que admite otros tipos, y también admite clases de usuario al duplicar los atributos en una nueva clase duplicada, y también bloquea la recursión infinita con una referencia a un objeto que ya se ha visto usando una lista de notas o un diccionario. Y eso es realmente para hacer copias profundas. En esencia, hacer una copia profunda es solo hacer copias superficiales. Espero que esta respuesta agregue algo a la pregunta.

EJEMPLOS

Digamos que tiene esta lista: [1, 2, 3] . Los números inmutables no pueden duplicarse, pero la otra capa sí. Puede duplicarlo utilizando una lista de comprensión: [x para x en [1, 2, 3]

Ahora, imagine que tiene esta lista: [[1, 2], [3, 4], [5, 6]] . Esta vez, desea hacer una función, que utiliza la recursividad para copiar en profundidad todas las capas de la lista. En lugar de la comprensión de la lista anterior:

[x for x in _list]

Utiliza uno nuevo para las listas:

[deepcopy_list(x) for x in _list]

Y deepcopy_list tiene este aspecto:

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

Entonces ahora tiene una función que puede hacer una copia en profundidad de cualquier lista de strs, bools, floast, ints e incluso listas de infinitas capas usando recursividad. Y ahí lo tienes, copiando en profundidad.

TLDR : Deepcopy utiliza la recursividad para duplicar objetos, y simplemente devuelve los mismos objetos inmutables que antes, ya que los objetos inmutables no pueden duplicarse. Sin embargo, copia en profundidad las capas más internas de objetos mutables hasta que alcanza la capa mutable más externa de un objeto.

Corman
fuente
3

Una ligera perspectiva práctica para mirar en la memoria a través de id y 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.
fuente
3

Recuerda eso en Python cuando lo haces:

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

List2 no almacena la lista real, sino una referencia a list1. Entonces, cuando haces algo para list1, list2 también cambia. use el módulo de copia (no predeterminado, descarga en pip) para hacer una copia original de la lista ( copy.copy()para listas simples, copy.deepcopy()para las anidadas). Esto hace una copia que no cambia con la primera lista.

Dr. hipopótamo
fuente
1

La opción de copia profunda es el único método que funciona para mí:

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

conduce a la salida de:

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

Esto se debe a que la línea new_list = my_listasigna una nueva referencia a la variable my_listque new_list es similar al Ccódigo que se muestra a continuación,

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

Debe usar el módulo de copia para crear una nueva lista por

import copy
new_list = copy.deepcopy(my_list)
Roshin Raphel
fuente