python - tamaño - ¿Cómo clonar o copiar una lista?




tamaño de una lista python (12)

¿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 la lista superficial

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

Python 3

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

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.

Al usar new_list = my_list, se modifica new_list cada vez que cambia my_list. ¿Por qué es esto?

my_list es solo un nombre que apunta a la lista real en la memoria. Cuando dices new_list = my_list no estás haciendo una copia, solo estás 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 serie 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 deepcopy en el módulo de copy :

import copy
a_deep_copy = copy.deepcopy(a_list)

Para demostrar cómo esto nos permite hacer nuevas sub-listas:

>>> 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. Puedes 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 en profundidad de la biblioteca estándar.

No use eval

Puede ver esto usado como una forma de copia en profundidad, pero no lo haga:

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

En 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

en 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

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

Al usar new_list = my_list se modifica new_list cada vez que cambia my_list .
¿Por qué es esto?


Empecemos desde el principio y exploremos un poco más profundo:

Supongamos que tienes dos listas:

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

Y tenemos que copiar ambas listas, ahora a partir de la primera lista:

Así que primero probemos por el método general de copia:

copy=list_1

Ahora, si está pensando en copiar, copie la lista_1, entonces puede estar equivocado, revisémoslo:

The id() function shows us that both variables point to the same list object, i.e. they share this object.
print(id(copy))
print(id(list_1))

salida:

4329485320
4329485320

Sorprendido? Ok, vamos a explorarlo:

Entonces, como sabemos, Python no almacena nada en una variable, las Variables solo hacen referencia al objeto y al objeto almacenan el valor. Aquí el objeto es una list pero creamos dos referencias a ese mismo objeto con dos nombres de variables diferentes. Entonces ambas variables apuntan al mismo objeto:

así que cuando haces copy=list_1 lo que realmente está haciendo:

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

Por lo tanto, si intenta modificar la lista copiada, también modificará la lista original, ya que la lista es solo una, la modificará desde la lista copiada o desde la lista original:

copy[0]="modify"

print(copy)
print(list_1)

salida:

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

Así se modificó la lista original:

Entonces, cual es la solución?

Solución:

Ahora vamos a pasar a un segundo método pitón para copiar la lista:

copy_1=list_1[:]

Ahora, este método arregla lo que enfrentábamos en el primer problema, comprobémoslo:

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

4338792136
4338791432

Entonces, como podemos ver que nuestras dos listas tienen una identificación diferente y significa que ambas variables apuntan a diferentes objetos, entonces lo que realmente está pasando aquí es:

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

copy_1[0]="modify"

print(list_1)
print(copy_1)

Salida:

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

Entonces, como pueden ver, no está modificando la lista original, solo modificó la lista copiada, así que estamos de acuerdo con ella.

Así que ahora creo que hemos terminado? espera, tenemos que copiar la segunda lista anidada también, así que vamos a probar de forma pythonic:

copy_2=list_2[:]

Así que list_2 debería hacer referencia a otro objeto que es copia de list_2. Verifiquemos:

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 intentemos modificarlo y veamos que está dando lo que queremos:

Así que cuando intentamos:

copy_2[0][1]="modify"

print(list_2,copy_2)

Nos da salida:

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

Ahora, esto es un poco confuso, usamos la forma pitónica y aún así, estamos enfrentando el mismo problema.

entendámoslo:

Así que cuando hacemos:

copy_2=list_2[:]

en realidad estamos copiando solo la lista externa, no la lista anidada, por lo que la lista anidada es el mismo objeto para ambas listas, revisemos:

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

salida:

4329485832
4329485832

Entonces, en realidad, cuando hacemos copy_2=list_2[:] esto es lo que sucede:

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, así que si intenta modificar la lista anidada, también modificará la lista original porque la lista de objetos anidados es igual para ambas. lista anidada

¿Entonces, cuál es la solución?

La solución es deep copy

from copy import deepcopy
deep=deepcopy(list_2)

Así que ahora vamos a comprobarlo:

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

salida:

4322146056 4322148040

ambas identificaciones son diferentes, ahora verifiquemos la identificación de la lista anidada:

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

salida:

4322145992
4322145800

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

Entonces, cuando haces deep=deepcopy(list_2) lo que realmente sucede:

Por lo tanto, ambas listas anidadas apuntan a objetos diferentes y ahora tienen una copia por separado de la lista anidada.

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

así que si lo hacemos:

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

salida:

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

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

Si le gusta mi respuesta detallada, hágamelo saber mediante la votación, si tiene alguna duda sobre esta respuesta, comente abajo :)


A diferencia de otros idiomas que tienen variables y valores , Python tiene nombre y objeto .

Esta declaración:

a = [1,2,3]

significa dar a la lista (objeto) un nombre a , y, esto:

b = a

simplemente le da al mismo objeto a nuevo nombre b , así que cada vez que haga algo con a , el objeto cambia y, por tanto, b cambia.

La única forma de hacer una copia real de una es crear un nuevo objeto como han dicho otras respuestas.

Puedes ver más sobre esto here .


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

Para copiar realmente la lista, tienes varias posibilidades:

  • Puede usar el método list.copy() (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 ) acerca de esto es que es una sintaxis extraña y que no tiene sentido usarla nunca . ;) (En su opinión, el siguiente es más legible).

  • Puedes usar la función integrada en la list() :

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

    import copy
    new_list = copy.copy(old_list)
    

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

  • Si la lista contiene objetos y también desea copiarlos, use copy.deepcopy() 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 inevitable.

Ejemplo:

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

Resultado:

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

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

  1. 10.59 sec (105.9us / itn) - copy.deepcopy(old_list)
  2. 10.16 sec (101.6us / itn): método Copy() python puro que copia las clases con copia profunda
  3. 1.488 seg (14.88us / itn) - Método Copy() python puro que no copia clases (solo dicts / listas / tuplas)
  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 lista de comprensión )
  6. 0.186 seg (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) new_list = []; new_list.extend(old_list)
  9. 0.039 sec (0.39us / itn) - old_list[:] ( corte de lista )

Así que lo más rápido es cortar la lista. Pero tenga en cuenta que copy.copy() , list[:] y list(list) , a diferencia de copy.deepcopy() y la versión de python, no copian ninguna lista, diccionario o instancia de clase en la lista, por lo que si los originales cambian , cambiarán tambié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

EDITAR : Se agregaron clases y dictados de estilo antiguo y estilo nuevo a los puntos de referencia, se hizo la versión de python mucho más rápida y se agregaron algunos métodos más, incluyendo expresiones de lista y extend() .



No estoy seguro de si esto sigue siendo real, pero el mismo comportamiento también se aplica a los diccionarios. Mira este ejemplo.

a = {'par' : [1,21,3], 'sar' : [5,6,8]}
b = a
c = a.copy()
a['har'] = [1,2,3]

a
Out[14]: {'har': [1, 2, 3], 'par': [1, 21, 3], 'sar': [5, 6, 8]}

b
Out[15]: {'har': [1, 2, 3], 'par': [1, 21, 3], 'sar': [5, 6, 8]}

c
Out[16]: {'par': [1, 21, 3], 'sar': [5, 6, 8]}

Puedes usar bulit en la función list ():

newlist=list(oldlist)

Creo que este código te ayudará.


Todos los demás colaboradores 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 al Objetos de list anidadas cuando trabaja con listas multidimensionales, anidadas (lista de listas). Si bien refiere a esto en su respuesta, hay un poco más sobre el problema y posiblemente una solución alternativa que utilice elementos integrados que podrían ser una alternativa más rápida a la 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 señalar los objetos de list anidados dentro de old_list y new_list , y cambian a uno De la list objetos se perpetúan en el otro.

Edit: Nueva información traída a la luz

Como lo señalaron tanto como PM 2Ring, el uso de eval() no solo es una mala idea, sino que es mucho más lento que 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 hacia el sur cuando intentas usarlo en una matriz multidimensional de tamaño moderado. Intenté timeit uso de una matriz 42x42, que no es inaudita ni tan grande para las aplicaciones de bioinformática, y renuncié a esperar una respuesta y comencé a escribir mi edición en esta publicación.

Parece 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 multidimensional de listas, se agradecerá.

Como han dicho otros, puede haber problemas de rendimiento significativos con el módulo de copy y copy.deepcopy para listas multidimensionales . Tratando de encontrar una forma diferente de copiar la lista multidimensional sin usar deepcopy (estaba trabajando en un problema para un curso que solo permite 5 segundos para que se ejecute todo el algoritmo para recibir crédito), se me ocurrió una manera de usar funciones incorporadas para hacer una copia de la lista anidada sin tener que apuntar entre sí o en la list objetos anidados dentro de ellas. Utilicé eval() y repr() en la asignación para hacer la copia de la lista antigua en la lista nueva sin crear un enlace a la lista antigua. Toma la forma de:

new_list = eval(repr(old_list))

Básicamente, lo que hace es hacer una representación de old_list como una cadena y luego evalúa la cadena como si fuera el objeto que representa la cadena. Al hacer esto, no se crea un enlace al objeto de list original. Se crea un nuevo objeto de list y cada variable apunta a su propio objeto independiente. Aquí hay un ejemplo usando una lista anidada bidimensional.

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

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

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

Si luego verifica el contenido de cada lista, por ejemplo, una lista de 4 por 3, Python devolverá

>>> new_list

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

>>> old_list

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

Si bien esto probablemente no sea la forma canónica o sintácticamente correcta de hacerlo, parece funcionar bien. No he probado el rendimiento, pero voy a suponer que eval() y rep() tendrán menos sobrecarga que ejecutar que deepcopy .


Un enfoque muy simple independiente de la versión de python faltaba en las respuestas ya dadas que puede usar la mayor parte del tiempo (al menos yo 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 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

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

new_list = my_list[:]

Entendamos la diferencia entre la Solución # 1 y la Solució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 # 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

Ya hay muchas respuestas que te dicen cómo hacer una copia correcta, pero ninguna de ellas dice por qué falló tu "copia" original.

Python no almacena valores en variables; enlaza nombres a objetos. Su asignación original tomó el objeto al que hace referencia my_list y también lo vinculó a new_list . my_list nombre que utilice, todavía hay una sola lista, por lo que los cambios realizados al referirse a ella como my_list persistirán cuando se new_list referencia a ella como new_list . Cada una de las otras respuestas a esta pregunta le brinda diferentes formas de crear un nuevo objeto para vincularlo a new_list .

Cada elemento de una lista actúa como un nombre, en el sentido de que cada elemento se enlaza 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 se refiere su lista y enlace 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 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 otro objeto al que hace referencia cada elemento, y así sucesivamente: realice una copia en profundidad.

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 los casos de esquina en la copia.


new_list = list(old_list)







clone