Enumere los cambios inesperados después de la asignación. ¿Por qué es esto y cómo prevenirlo?

Durante el uso new_list = my_list, cualquier modificación a new_list cambios my_list cada vez. ¿Por qué es esto y cómo puedo clonar o copiar la lista para evitarlo?

preguntado el 10 de abril de 10 a las 05:04

22 Respuestas

Con new_list = my_list, en realidad no tienes dos listas. La tarea simplemente copia la referencia a la lista, no la lista real, por lo que ambos new_list y my_list consulte la misma lista después de la asignación.

Para copiar realmente la lista, tiene varias posibilidades:

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

    new_list = old_list.copy()
    
  • Puedes cortarlo:

    new_list = old_list[:]
    

    Alex Martelli opinión (al menos de nuevo 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).

  • Puedes usar el incorporado list() función:

    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 el tipo de datos de old_list de antemano.

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

    import copy
    new_list = copy.deepcopy(old_list)
    

    Obviamente, el método más lento y que más memoria necesita, pero a veces es 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)]

respondido 09 mar '20, 01:03

Como @Georgy señala correctamente en la respuesta a continuación, cualquier cambio en los valores de new_list también cambiará los valores en my_list. Entonces, en realidad, el método copy.deepcopy () es la única copia real sin referencia a la lista original y sus valores. - moojen

@Erri Creo que cometiste un error. No publiqué ninguna respuesta o comentario aquí :) - Georgy

Tienes razón, fue editado por ti, pero publicado por @cryo ¡Perdón por la confusión! - moojen

Felix ya proporcionó una respuesta excelente, pero pensé en hacer una comparación de velocidad de los distintos métodos:

  1. 10.59 segundos (105.9us / itn) - copy.deepcopy(old_list)
  2. 10.16 segundos (101.6us / itn) - Python puro Copy() método de copia de clases con deepcopy
  3. 1.488 segundos (14.88us / itn) - Python puro Copy() método que 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 segundos (2.17us / itn) - [i for i in old_list] (a 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 segundos (0.39us / itn) - old_list[:] (corte de lista)

Entonces, lo más rápido es la división de listas. Pero ten en cuenta que copy.copy(), list[:] y list(list), diferente a copy.deepcopy() y la versión de Python no copia ninguna lista, diccionario ni 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 guión 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

contestado el 12 de mayo de 19 a las 15:05

He sido dicho que Python 3.3+ añade list.copy() método, que debería ser tan rápido como cortar:

newlist = old_list.copy()

Respondido 05 Abr '17, 02:04

Sí, y según los documentos. docs.python.org/3/library/stdtypes.html#mutable-sequence-types, s.copy() crea una copia superficial de s (igual que s[:]). - CyberMew

De hecho, parece que actualmente python3.8, .copy() is un poco más rápido que rebanar. Vea a continuación la respuesta de @AaronsHall. - amado.por.Jesús

@ amado.by.Jesús: Sí, ellos optimizaciones agregadas para llamadas a métodos de nivel de Python en 3.7 que se extendieron a Llamadas al método de extensión C en 3.8 por PEP 590 que eliminan la sobrecarga de crear un método vinculado cada vez que llama a un método, por lo que el costo de llamar alist.copy() ahora es un dict buscar en el list type, luego una llamada de función sin argumentos relativamente barata que finalmente invoca lo mismo que slicing. Cortar todavía tiene que construir un slice objeto, luego realice comprobaciones de tipo y desempaque para hacer lo mismo. - ShadowRanger

Por supuesto, están trabajando en optimizar las compilaciones repetidas de cortes constantes, por lo que en 3.10 rebanar podría ganar de nuevo. Sin embargo, todo es bastante insignificante; el rendimiento asintótico es idéntico y la sobrecarga fija relativamente pequeña, por lo que realmente no importa qué enfoque utilice. - ShadowRanger

¿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 de 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 propia lista, que es un contenedor de referencias a los objetos de la lista. Si los objetos contenidos en sí mismos son mutables y se cambia uno, 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.

2 Python

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

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

3 Python

En Python 3, las listas obtienen el list.copy mé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 lo hace no Hacer una copia

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

my_list es solo un nombre que apunta a la lista real en la memoria. Cuando tu dices new_list = my_list no está haciendo una copia, solo está agregando otro nombre que apunta a esa lista original en la memoria. Podemos tener problemas similares cuando hacemos copias de 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 al contenido, 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

Hacer un copia profunda de una lista, en Python 2 o 3, use deepcopy en la copy módulo:

import copy
a_deep_copy = copy.deepcopy(a_list)

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

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

Y entonces vemos que la lista de copias profundas es una lista completamente diferente de la original. Podrías lanzar 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 use eval

Puede ver que esto se usa como una forma de realizar copias en profundidad, 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 eficaz.

En Python 64 de 2.7 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 64 de 3.5 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

Respondido el 09 de enero de 18 a las 15:01

No necesita una copia en profundidad 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

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 mencionado por my_list y atado a new_list también. Independientemente del nombre que utilice, solo hay una lista, por lo que los cambios realizados al referirse a ella como my_list persistirá 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 enlazar new_list.

Cada elemento de una lista actúa como un nombre, en el sentido de que cada elemento se une de forma no exclusiva 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 lista un paso más allá, copie cada objeto al que se refiere 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 todavía no es una copia profunda, porque cada elemento de una lista puede hacer referencia a otros objetos, al igual que la lista está vinculada a sus elementos. Para copiar de forma recursiva todos los elementos de la lista, y luego cada otro objeto al que hace referencia 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)

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

Respondido el 05 de junio de 20 a las 17:06

Debería haberme desplazado hacia abajo hasta su respuesta al tratar este tema. Me ha costado mucho intentar averiguar por qué mi copia de la "lista de listas" en realidad no es una copia ... :-) ¡Gracias por esta! - SrZH6

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:

Así que primero intentemos configurando la variable copy a nuestra lista original, list_1:

copy=list_1

Ahora, si estás pensando que copy copió list_1, entonces estás equivocado. La id La funció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 que 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 list pero creamos dos referencias a ese mismo objeto con dos nombres de variable diferentes. Esto significa que ambas variables apuntan al mismo objeto, solo que con nombres diferentes.

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

enter image description here

Aquí en la imagen list_1 y copy hay dos nombres de variables, pero el objeto es el mismo para ambas variables, que list

Entonces, si intenta modificar la lista copiada, también modificará la lista original porque la lista es solo una allí, modificará esa lista sin importar lo que haga de la lista copiada o de 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 que nuestra lista tiene una identificación diferente, significa que ambas variables apuntan a objetos diferentes. Entonces, lo que realmente está sucediendo aquí es:

enter image description here

Ahora intentemos modificar la lista y veamos si aún nos enfrentamos al 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_2 debe 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 asumir que ambas listas apuntan a un objeto diferente, así que ahora intentemos 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 usamos 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 el id función una vez más para comprobar esto.

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

El resultado es:

4329485832
4329485832

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

enter image description here

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 solucion es la deepcopy función.

from copy import deepcopy
deep=deepcopy(list_2)

Revisemos esto:

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

4322146056 4322148040

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

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

El resultado es:

4322145992
4322145800

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

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

enter image description here

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

contestado el 05 de mayo de 20 a las 23:05

Utiliza la thing[:]

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

Respondido 10 Abr '10, 09:04

Tiempos de Python 3.6

Estos son los resultados de sincronización con 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 Python3 corte equivalente) y dos formas de lista de desembalaje (*new_list, = list y 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 a Python3 list.copy() por mucho, especialmente considerando la legibilidad superior de este último.

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

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

Tenga en cuenta que estos métodos no generar resultados equivalentes para cualquier entrada que no sean listas Todos funcionan para objetos divisibles, algunos funcionan para cualquier iterable, pero solo copy.copy() funciona 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))

contestado el 14 de mayo de 19 a las 03:05

Puedo confirmar todavía una historia similar en 3.8 b=[*a] - la única forma obvia de hacerlo;). - superdisparo

Algunas de estas comparaciones de tiempo no son particularmente significativas al copiar listas tan pequeñas. Sería más informativo probar con una variedad de longitudes de lista (incluidas algunas muy grandes). - ehumoro

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

Respondido 10 Abr '10, 09:04

Todos los demás contribuyentes dieron maravillosa 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 al anidado list objetos cuando se trabaja con listas anidadas multidimensionales (lista de listas). Tiempo Félix Kling se refiere a él en su respuesta, hay un poco más sobre el problema y posiblemente una solución alternativa utilizando elementos integrados que podrían resultar una alternativa más rápida a deepcopy.

Aunque la 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 la list objetos anidados dentro del old_list y new_listy cambia a uno de los list los objetos se perpetúan en el otro.

Editar: nueva información sacada a la luz

Como lo señalaron ambos salón aarón y PM 2 Anillo usar eval() no solo es una mala idea, también es mucho más lento que copy.deepcopy().

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

Parecería que la única opción real es inicializar varias listas y trabajar en ellas de forma independiente. Si alguien tiene alguna otra sugerencia sobre cómo manejar la copia de listas multidimensionales, sería apreciada.

Como han dicho otros, hay son importantes problemas de rendimiento con el copy módulo y copy.deepcopy para listas multidimensionales.

Respondido el 20 de junio de 20 a las 10:06

Esto no siempre funcionará, ya que no hay garantía de que la cadena devuelta por repr() es suficiente para recrear el objeto. También, eval() es una herramienta de último recurso; ver Eval realmente es peligroso por SO veterano Ned Batchelder para más detalles. Entonces, cuando defiende el uso eval() democracia Debería mencionar que puede ser peligroso. - PM 2 Anillo

Punto justo. Aunque creo que el punto de Batchelder es que tener la eval() La función en Python en general es un riesgo. No se trata tanto de si hace uso o no de la función en el código, sino de que es un agujero de seguridad en Python en sí mismo. Mi ejemplo no lo usa con una función que recibe entrada de input(), sys.agrvo incluso un archivo de texto. Es más en la línea de inicializar una lista multidimensional en blanco una vez, y luego simplemente tener una forma de copiarla en un ciclo en lugar de reinicializar en cada iteración del ciclo. - AMR

Como ha señalado @AaronHall, es probable que el uso de new_list = eval(repr(old_list)), así que además de ser una mala idea, probablemente también sea demasiado lento para funcionar. - AMR

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

Puede realizar el desembalaje de la lista con el "operador de 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, en cuanto al tiempo, 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)

Respondido 26 Feb 18, 02:02

¿Cómo se comporta este método al modificar copias? - no2qubit

@ not2qubit ¿te refieres a agregar o editar elementos de la nueva lista? En el ejemplo old_list y new_list son 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

Faltaba un enfoque muy simple independiente de la versión de Python en las respuestas ya dadas que puede usar la mayor parte del tiempo (al menos yo):

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

.Bono: Si no desea copiar elementos, use (también conocido como copia superficial):

new_list = my_list[:]

Entendamos 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é pasará 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

respondido 01 nov., 17:08

Tenga en cuenta que hay algunos casos en los que si ha definido su propia clase personalizada y desea mantener los atributos, debe usar copy.copy() or copy.deepcopy() en lugar de 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

contestado el 16 de mayo de 18 a las 15:05

new_list = my_list[:]

new_list = my_list Trate de entender esto. Digamos que my_list está en la memoria del montón en la ubicación X, es decir, my_list está apuntando a la X. Ahora asignando new_list = my_list está dejando que new_list apunte a la X. Esto se conoce como copia superficial.

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

Respondido el 26 de junio de 17 a las 22:06

Quería publicar algo un poco diferente a algunas de las otras respuestas. Aunque probablemente 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 de esto 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.

En el núcleo de cualquier función de copia profunda se encuentra la forma de hacer una copia superficial. ¿Cómo? Sencillo. Cualquier función de copia profunda solo duplica los contenedores de objetos inmutables. Cuando realiza una copia profunda de una lista anidada, solo está duplicando las listas externas, no los objetos mutables dentro de las listas. Solo está 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 tienes que copiar los contenedores, como listas, dictados, tuplas, iters, clases e instancias de clases?

Es sencillo. Un objeto mutable realmente no se puede duplicar. No se puede cambiar nunca, por lo que es solo un valor. Eso significa que nunca tendrá que duplicar cadenas, números, bools o cualquiera de esos. Pero, ¿cómo duplicarías los contenedores? Sencillo. Simplemente inicializa un nuevo contenedor con todos los valores. Deepcopy se basa en la recursividad. Duplica todos los contenedores, incluso los que tienen contenedores dentro, hasta que no quedan contenedores. Un contenedor es un objeto inmutable.

Una vez que sepa eso, duplicar completamente un objeto sin referencias es bastante fácil. Aquí hay una función para copiar en profundidad 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 incorporada 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 recursividad infinita con una referencia a un objeto que ya se ha visto usando una lista de notas o un diccionario. Y eso es todo 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 se pueden duplicar, pero la otra capa sí. Puede duplicarlo usando una lista de comprensión: [x para x en [1, 2, 3]

Ahora, imagina que tienes esta lista: [[1, 2], [3, 4], [5, 6]]. Esta vez, desea crear una función que utilice 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 copia_profunda_lista Se ve como esto:

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 copiar en profundidad cualquier lista de strs, bools, flotar, ints e incluso liza a un número infinito de capas utilizando la recursividad. Y ahí lo tienes, copiando en profundidad.

TLDR: Deepcopy usa la recursividad para duplicar objetos y simplemente devuelve los mismos objetos inmutables que antes, ya que los objetos inmutables no se pueden duplicar. 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.

Respondido el 08 de Septiembre de 19 a las 03:09

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) 

respondido 23 nov., 19:19

Recuerda eso en Python cuando lo hagas:

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

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

Respondido 22 Feb 20, 12:02

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

Respondido 11 Abr '20, 12:04

deepcopy debe usarse solo cuando sea necesario y uno debe ser consciente de lo que realmente hace. - Jean-François Fabre ♦

Esto se debe a que la línea new_list = my_list asigna una nueva referencia a la variable my_list cual es new_list Esto es similar a la C código dado a continuación,

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

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

import copy
new_list = copy.deepcopy(my_list)

Respondido el 04 de junio de 20 a las 11:06

El método a utilizar depende del contenido de la lista que se está copiando. Si la lista contiene anidadas dicts que la copia profunda es el único método que funciona; de lo contrario, la mayoría de los métodos enumerados en las respuestas (cortar, repetir [para], copiar, extender, combinar o desempaquetar) funcionarán y se ejecutarán en un tiempo similar (excepto para bucle y copia profunda, que preformado lo peor).

Guión

from random import randint
from time import time
import copy

item_count = 100000

def copy_type(l1: list, l2: list):
  if l1 == l2:
    return 'shallow'
  return 'deep'

def run_time(start, end):
  run = end - start
  return int(run * 1000000)

def list_combine(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = [] + l1
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'combine', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_extend(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = []
  l2.extend(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'extend', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_unpack(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = [*l1]
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'unpack', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_deepcopy(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = copy.deepcopy(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'deepcopy', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_copy(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = list.copy(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'copy', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_slice(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = l1[:]
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'slice', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_loop(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = []
  for i in range(len(l1)):
    l2.append(l1[i])
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'loop', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

def list_list(data):
  l1 = [data for i in range(item_count)]
  start = time()
  l2 = list(l1)
  end = time()
  if type(data) == dict:
    l2[0]['test'].append(1)
  elif type(data) == list:
    l2.append(1)
  return {'method': 'list()', 'copy_type': copy_type(l1, l2), 
          'time_µs': run_time(start, end)}

if __name__ == '__main__':
  list_type = [{'list[dict]': {'test': [1, 1]}}, 
          {'list[list]': [1, 1]}]
  store = []
  for data in list_type:
    key = list(data.keys())[0]
    store.append({key: [list_unpack(data[key]), list_extend(data[key]), 
                list_combine(data[key]), list_deepcopy(data[key]), 
                list_copy(data[key]), list_slice(data[key]),           
                list_loop(data[key])]})
  print(store)

Resultados

[{"list[dict]": [
  {"method": "unpack", "copy_type": "shallow", "time_µs": 56149},
  {"method": "extend", "copy_type": "shallow", "time_µs": 52991},
  {"method": "combine", "copy_type": "shallow", "time_µs": 53726},
  {"method": "deepcopy", "copy_type": "deep", "time_µs": 2702616},
  {"method": "copy", "copy_type": "shallow", "time_µs": 52204},
  {"method": "slice", "copy_type": "shallow", "time_µs": 52223},
  {"method": "loop", "copy_type": "shallow", "time_µs": 836928}]},
{"list[list]": [
  {"method": "unpack", "copy_type": "deep", "time_µs": 52313},
  {"method": "extend", "copy_type": "deep", "time_µs": 52550},
  {"method": "combine", "copy_type": "deep", "time_µs": 53203},
  {"method": "deepcopy", "copy_type": "deep", "time_µs": 2608560},
  {"method": "copy", "copy_type": "deep", "time_µs": 53210},
  {"method": "slice", "copy_type": "deep", "time_µs": 52937},
  {"method": "loop", "copy_type": "deep", "time_µs": 834774}
]}]

Respondido el 30 de enero de 21 a las 20:01

Existe una técnica simple para manejar esto.

Código:

number=[1,2,3,4,5,6] #Original list
another=[] #another empty list
for a in number: #here I am declaring variable (a) as an item in the list (number)
    another.append(a) #here we are adding the items of list (number) to list (another)
print(another)

Salida:

>>> [1,2,3,4,5,6]

Espero que esto haya sido útil para su consulta.

Respondido 03 Jul 20, 04:07

Hay otra forma de copiar una lista que no estaba en la lista hasta ahora: agregando una lista vacía: l2 = l + []. Lo probé con Python 3.8:

l = [1,2,3]
l2 = l + []
print(l,l2)
l[0] = 'a'
print(l,l2)

No es la mejor respuesta, pero funciona.

Respondido 03 Jul 20, 14:07

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.