Pregunta Cómo clonar o copiar una lista?


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

Utilizando new_list = my_list luego modifica new_list cada vez my_list cambios.
¿Por qué es esto?


1690
2018-04-10 08:49


origen


Respuestas:


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 ambos new_list y my_list consulte la misma lista después de la asignación.

Para copiar realmente la lista, tienes 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 allá en 2007) sobre esto es, que es una sintaxis extraña y no tiene sentido usarla alguna vez. ;) (En su opinión, el siguiente es más legible).

  • Puedes usar el built-in list() función:

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

    import copy
    new_list = copy.copy(old_list)
    

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

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

    import copy
    new_list = copy.deepcopy(old_list)
    

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

2315
2018-04-10 08:55



Félix ya me brindó una excelente respuesta, pero pensé que haría una comparación de velocidad de varios métodos:

  1. 10.59 seg (105.9us / itn) - copy.deepcopy(old_list)
  2. 10.16 segundos (101.6us / itn) - pitón puro Copy() método para copiar clases con deepcopy
  3. 1.488 segundos (14.88us / itn) - pitón puro Copy() método no copia clases (solo dicts / lists / tuples)
  4. 0.325 seg (3.25us / itn) - for item in old_list: new_list.append(item)
  5. 0.217 seg (2.17us / itn) - [i for i in old_list] (un lista de comprensión)
  6. 0.186 seg (1.86us / itn) - copy.copy(old_list)
  7. 0.075 seg (0.75us / itn) - list(old_list)
  8. 0.053 seg (0.53us / itn) - new_list = []; new_list.extend(old_list)
  9. 0.039 seg (0.39us / itn) - old_list[:] (list slicing)

Por lo tanto, el más rápido es dividir en listas. Pero ten en cuenta que copy.copy(), list[:] y list(list), diferente a copy.deepcopy() y la versión de Python no copia listas, diccionarios e instancias 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

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


447
2018-04-10 10:16



He sido contado que Python 3.3+ agrega list.copy() método, que debe ser tan rápido como cortar:

newlist = old_list.copy()


116
2017-07-23 12:32



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

Hay diferentes maneras 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 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 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 no no Hacer una copia

Usar new_list = my_list luego modifica new_list cada vez que my_list cambie. ¿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á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 los mismos contenidos. 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 el copy módulo:

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 entonces vemos que la lista copiada profundamente es una lista completamente diferente del original. Podrías rodar tu propia función, pero no lo haces. Es probable que cree errores que de otro modo no tendría al usar la función de copia profunda de la biblioteca estándar.

No usar eval

Puede ver esto 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á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 pueda evaluarse para reproducir un elemento equivalente.
  3. También es menos eficiente.

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

87
2017-10-25 12:13



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

Python no almacena valores en variables; enlaza nombres a objetos. Su asignación original tomó el objeto mencionado por my_list y atado a new_list también. No importa qué nombre use, todavía hay una sola lista, por lo que se hacen cambios cuando se hace referencia a ella como my_list persistirá cuando se refiera a él como new_list. Cada una de las otras respuestas a esta pregunta te brinda diferentes formas de crear un nuevo objeto para vincular a 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 lista a copiar un paso más, 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 no es aún una copia profunda, porque cada elemento de una lista puede referirse a otros objetos, al igual que la lista está ligada 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)

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


42
2017-11-23 16:45



new_list = list(old_list)


30
2018-04-10 09:03



Utilizar thing[:]

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

27
2018-04-10 08:53



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


26
2018-04-10 08:53



Todos los demás colaboradores dieron estupendo 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() trabaja para clonar / copiar una lista y no hacer que apunte a la anidada list objetos cuando trabaja con listas anidadas multidimensionales (lista de listas). Mientras Felix Kling se refiere a esto en su respuesta, hay un poco más sobre el tema y posiblemente una solución alternativa usando componentes integrados que podría ser una alternativa más rápida a deepcopy.

Mientras new_list = old_list[:], copy.copy(old_list)' y para Py3k old_list.copy() trabajar para listas de un solo nivel, vuelven a señalar al list objetos anidados dentro del old_list y el new_listy cambios a uno de los list los objetos se perpetúan en el otro.

Editar: Nueva información sacada a la luz

Como fue señalado por ambos Aaron Hall y PM 2Ring  utilizando eval() no solo es una mala idea, también es mucho más lenta que copy.deepcopy(). 

Esto significa que para listas multidimensionales, la única opción es copy.deepcopy(). Habiendo dicho eso, realmente no es una opción ya que el rendimiento va mucho más al sur cuando intentas usarlo en una matriz multidimensional de tamaño moderado. lo intenté timeit usando una matriz 42x42, no desconocida o incluso tan grande para aplicaciones de bioinformática, 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 múltiples listas y trabajar en ellas de forma independiente. Si alguien tiene alguna otra sugerencia, sobre cómo manejar la copia de listas multidimensional, sería de agradecer.

Como otros han declarado, hay puede ser  son significativos problemas de rendimiento utilizando el copy módulo 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 todo el algoritmo se ejecute para recibir crédito), se me ocurrió una forma de usar las funciones integradas para hacer una copia de la lista anidada sin haciéndoles apuntar el uno al otro o al list objetos anidados dentro de ellos. solía eval() y repr() en la tarea de hacer una copia de la lista anterior en la nueva lista sin crear un enlace a la lista anterior. 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 hay un enlace al original list objeto está hecho. Un nuevo list objeto se crea 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 regresará

>>> 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 esta no es probablemente la manera canónica o sintácticamente correcta de hacerlo, parece funcionar bien. No he probado el rendimiento, pero voy a adivinar eval() y rep() tendrá menos gastos generales para ejecutar que deepcopy será.


18
2017-07-10 03:51