Como clonar ou copiar uma lista?

2549

Quais são as opções para clonar ou copiar uma lista no Python?

Durante o uso new_list = my_list, todas as modificações nas new_listalterações são my_listsempre alteradas . Por que é isso?

aF.
fonte

Respostas:

3330

Com new_list = my_list, você não tem duas listas. A atribuição apenas copia a referência à lista, não a lista real, portanto, ambas new_liste se my_listreferem à mesma lista após a atribuição.

Para realmente copiar a lista, você tem várias possibilidades:

  • Você pode usar o list.copy()método builtin (disponível desde o Python 3.3):

    new_list = old_list.copy()
  • Você pode cortá-lo:

    new_list = old_list[:]

    A opinião de Alex Martelli (pelo menos em 2007 ) sobre isso é que é uma sintaxe estranha e não faz sentido usá-lo sempre . ;) (Na sua opinião, o próximo é mais legível).

  • Você pode usar a list()função incorporada:

    new_list = list(old_list)
  • Você pode usar genérico copy.copy():

    import copy
    new_list = copy.copy(old_list)

    Isso é um pouco mais lento do que list()porque precisa descobrir o tipo de dados old_listprimeiro.

  • Se a lista contiver objetos e você também quiser copiá-los, use genérico copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)

    Obviamente, o método mais lento e com maior necessidade de memória, mas às vezes inevitável.

Exemplo:

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
fonte
7
Se não me engano: newlist = [*mylist]também é uma possibilidade no Python 3. newlist = list(mylist)talvez seja mais claro.
Stéphane
9
outra possibilidade é new_list = old_list * 1
aris
4
Quais desses métodos são cópia superficial e quais são cópia profunda?
Eswar 18/09/18
4
@Eswar: todos, mas o último fazer uma cópia superficial
Felix Kling
3
@ Swar é uma cópia superficial.
Juanpa.arrivillaga
604

Felix já forneceu uma excelente resposta, mas pensei em fazer uma comparação rápida dos vários métodos:

  1. 10,59 seg (105,9us / itn) - copy.deepcopy(old_list)
  2. 10.16 sec (101.6us / itn) - Copy()método python puro que copia classes com deepcopy
  3. 1.488 sec (14.88us / itn) - Copy()método python puro que não copia classes (apenas dict / 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](uma compreensão da lista )
  6. 0,186 seg (1,86 us / 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[:]( lista de fatias )

Portanto, o mais rápido é o fatiamento de lista. Mas esteja ciente de que copy.copy(), list[:]e list(list), ao contrário copy.deepcopy()ea versão python não copie quaisquer listas, dicionários e instâncias de classe na lista, por isso, se os originais mudar, eles vão mudar na lista copiado também e vice-versa.

(Aqui está o script, se alguém estiver interessado ou quiser levantar algum 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
fonte
9
Como você é referência, pode ser útil incluir um ponto de referência. Esses números ainda são precisos em 2017 usando o Python 3.6 com código totalmente compilado? Estou observando a resposta abaixo ( stackoverflow.com/a/17810305/26219 ) já questiona esta resposta.
Mark Edington
4
use o timeitmódulo Além disso, você não pode concluir muito de micro benchmarks arbitrários como este.
Corey Goldberg
3
Se você deseja incluir uma nova opção para 3.5+, [*old_list]deve ser aproximadamente equivalente a list(old_list), mas, como é sintaxe, não caminhos de chamada de função geral, economiza um pouco no tempo de execução (e old_list[:], diferentemente do que não digita convert, [*old_list]funciona em qualquer iterável e produz a list).
ShadowRanger
3
@CoreyGoldberg para um micro-benchmark um pouco menos arbitrário (usa timeit, 50m é executado em vez de 100k) veja stackoverflow.com/a/43220129/3745896
River
1
O @ShadowRanger [*old_list]parece superar quase qualquer outro método. (veja minha resposta vinculada nos comentários anteriores) #
Rio
151

Foi-me dito que o Python 3.3+ adicionalist.copy() método, que deve ser tão rápido quanto o fatiamento:

newlist = old_list.copy()

anatoly techtonik
fonte
6
Sim, e conforme docs docs.python.org/3/library/stdtypes.html#mutable-sequence-types , s.copy()cria uma cópia superficial de s(igual a s[:]).
CyberMew
Na verdade, parece que, atualmente python3.8, .copy()é um pouco mais rápido que fatiar. Veja abaixo a resposta do @AaronsHall.
loved.by.Jesus
126

Quais são as opções para clonar ou copiar uma lista no Python?

No Python 3, uma cópia superficial pode ser feita com:

a_copy = a_list.copy()

Nos Python 2 e 3, você pode obter uma cópia superficial com uma fatia completa do original:

a_copy = a_list[:]

Explicação

Existem duas maneiras semânticas de copiar uma lista. Uma cópia superficial cria uma nova lista dos mesmos objetos, uma cópia profunda cria uma nova lista contendo novos objetos equivalentes.

Cópia de lista rasa

Uma cópia superficial apenas copia a própria lista, que é um contêiner de referências aos objetos na lista. Se os objetos contidos em si forem mutáveis ​​e um for alterado, a alteração será refletida nas duas listas.

Existem diferentes maneiras de fazer isso no Python 2 e 3. As formas do Python 2 também funcionarão no Python 3.

Python 2

No Python 2, a maneira idiomática de fazer uma cópia superficial de uma lista é com uma fatia completa do original:

a_copy = a_list[:]

Você também pode realizar a mesma coisa passando a lista pelo construtor da lista,

a_copy = list(a_list)

mas usar o construtor é menos eficiente:

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

Python 3

No Python 3, as listas obtêm o list.copymétodo:

a_copy = a_list.copy()

No 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

Fazer outro ponteiro não faz uma cópia

O uso de new_list = my_list modifica a new_list toda vez que minha mylist for alterada. Por que é isso?

my_listé apenas um nome que aponta para a lista real na memória. Quando você diz new_list = my_listque não está fazendo uma cópia, está apenas adicionando outro nome que aponta para a lista original na memória. Podemos ter problemas semelhantes quando fazemos cópias de listas.

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

A lista é apenas uma matriz de ponteiros para o conteúdo; portanto, uma cópia superficial apenas copia os ponteiros e, portanto, você tem duas listas diferentes, mas elas têm o mesmo conteúdo. Para fazer cópias do conteúdo, você precisa de uma cópia profunda.

Cópias profundas

Para fazer uma cópia profunda de uma lista, em Python 2 ou 3, use deepcopyno copymódulo :

import copy
a_deep_copy = copy.deepcopy(a_list)

Para demonstrar como isso nos permite criar novas sub-listas:

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

E, portanto, vemos que a lista profunda copiada é uma lista totalmente diferente da original. Você pode rolar sua própria função - mas não. É provável que você crie bugs que de outra forma não teria usando a função deepcopy da biblioteca padrão.

Não use eval

Você pode ver isso usado como uma maneira de fazer uma cópia em profundidade, mas não faça isso:

problematic_deep_copy = eval(repr(a_list))
  1. É perigoso, principalmente se você estiver avaliando algo de uma fonte em que não confia.
  2. Não é confiável, se um subelemento que você está copiando não tiver uma representação que possa ser avaliada para reproduzir um elemento equivalente.
  3. Também é menos eficiente.

No 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

no 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
fonte
1
Você não precisa de uma cópia em profundidade se a lista for 2D. Se for uma lista de listas e essas listas não tiverem listas, você poderá usar um loop for. Atualmente, estou usando list_copy=[] for item in list: list_copy.append(copy(item))e é muito mais rápido.
John Locke
54

Já existem muitas respostas que explicam como fazer uma cópia adequada, mas nenhuma delas diz por que a sua 'cópia' original falhou.

Python não armazena valores em variáveis; liga nomes a objetos. Sua tarefa original pegou o objeto referido my_liste o vinculou new_listtambém. Não importa qual nome você use, ainda há apenas uma lista; portanto, as alterações feitas ao se referir a ele my_listpermanecerão quando se referir a ele comonew_list . Cada uma das outras respostas a esta pergunta fornece maneiras diferentes de criar um novo objeto ao qual se vincular.new_list .

Cada elemento de uma lista age como um nome, pois cada elemento se liga não exclusivamente a um objeto. Uma cópia superficial cria uma nova lista cujos elementos se ligam aos mesmos objetos de 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 levar sua lista a copiar um passo adiante, copie cada objeto a que sua lista se refere e vincule essas cópias de elemento a uma nova lista.

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

Essa ainda não é uma cópia detalhada, porque cada elemento de uma lista pode se referir a outros objetos, assim como a lista está vinculada a seus elementos. Para copiar recursivamente todos os elementos da lista e, em seguida, o outro objeto referido por cada elemento, e assim por diante: execute uma cópia profunda.

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

Consulte a documentação para obter mais informações sobre os casos de canto na cópia.

jack
fonte
38

Usar thing[:]

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

Vamos começar do começo e explorar esta questão.

Então, vamos supor que você tenha duas listas:

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

E temos que copiar as duas listas, agora começando na primeira lista:

Então, primeiro vamos tentar definindo a variável copyna nossa lista original list_1:

copy=list_1

Agora, se você está pensando que copiou a lista_1, está enganado. A idfunção pode nos mostrar se duas variáveis ​​podem apontar para o mesmo objeto. Vamos tentar isso:

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

A saída é:

4329485320
4329485320

Ambas as variáveis ​​são exatamente o mesmo argumento. Você está surpreso?

Portanto, como sabemos que o python não armazena nada em uma variável, as variáveis ​​estão apenas fazendo referência ao objeto e o objeto armazena o valor. Aqui objeto é um, listmas criamos duas referências a esse mesmo objeto por dois nomes de variáveis ​​diferentes. Isso significa que ambas as variáveis ​​estão apontando para o mesmo objeto, apenas com nomes diferentes.

Quando você faz copy=list_1, está realmente fazendo:

insira a descrição da imagem aqui

Aqui, na lista de imagens_1 e cópia, existem dois nomes de variáveis, mas o objeto é o mesmo para ambas as variáveis, o que é list

Portanto, se você tentar modificar a lista copiada, ela também modificará a lista original porque a lista é apenas uma lá, você modificará essa lista, independentemente da lista copiada ou da lista original:

copy[0]="modify"

print(copy)
print(list_1)

resultado:

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

Por isso, modificou a lista original:

Agora vamos passar para um método pitônico para copiar listas.

copy_1=list_1[:]

Este método corrige o primeiro problema que tivemos:

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

4338792136
4338791432

Então, como podemos ver nossa lista de ambos com um ID diferente e isso significa que ambas as variáveis ​​estão apontando para objetos diferentes. Então, o que realmente está acontecendo aqui é:

insira a descrição da imagem aqui

Agora vamos tentar modificar a lista e ver se ainda enfrentamos o problema anterior:

copy_1[0]="modify"

print(list_1)
print(copy_1)

A saída é:

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

Como você pode ver, apenas modificou a lista copiada. Isso significa que funcionou.

Você acha que terminamos? Não. Vamos tentar copiar nossa lista aninhada.

copy_2=list_2[:]

list_2deve fazer referência a outro objeto que é cópia list_2. Vamos checar:

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

Nós obtemos a saída:

4330403592 4330403528

Agora podemos assumir que ambas as listas estão apontando objetos diferentes, então agora vamos tentar modificá-lo e ver se está dando o que queremos:

copy_2[0][1]="modify"

print(list_2,copy_2)

Isso nos dá a saída:

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

Isso pode parecer um pouco confuso, porque o mesmo método que usamos anteriormente funcionou. Vamos tentar entender isso.

Quando você faz:

copy_2=list_2[:]

Você está apenas copiando a lista externa, não a lista interna. Podemos usar a idfunção mais uma vez para verificar isso.

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

A saída é:

4329485832
4329485832

Quando o fazemos copy_2=list_2[:], isso acontece:

insira a descrição da imagem aqui

Ele cria a cópia da lista, mas apenas a cópia da lista externa, não a cópia da lista aninhada, a lista aninhada é a mesma para as duas variáveis; portanto, se você tentar modificar a lista aninhada, também modificará a lista original, pois o objeto da lista aninhada é o mesmo para ambas as listas.

Qual é a solução? A solução é a deepcopyfunção.

from copy import deepcopy
deep=deepcopy(list_2)

Vamos verificar isso:

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

4322146056 4322148040

Ambas as listas externas têm IDs diferentes, vamos tentar isso nas listas internas aninhadas.

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

A saída é:

4322145992
4322145800

Como você pode ver, os dois IDs são diferentes, o que significa que podemos assumir que ambas as listas aninhadas estão apontando objetos diferentes agora.

Isso significa que quando você faz o deep=deepcopy(list_2)que realmente acontece:

insira a descrição da imagem aqui

Ambas as listas aninhadas estão apontando objetos diferentes e agora têm cópia separada da lista aninhada.

Agora vamos tentar modificar a lista aninhada e ver se ela resolveu o problema anterior ou não:

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

Emite:

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

Como você pode ver, não modificou a lista aninhada original, apenas modificou a lista copiada.

Aaditya Ura
fonte
34

O idioma do Python para fazer isso é newList = oldList[:]

erisco
fonte
34

Horário do Python 3.6

Aqui estão os resultados do tempo usando o Python 3.6.8. Lembre-se de que esses tempos são relativos um ao outro, não absolutos.

Eu continuei fazendo apenas cópias rasas e também adicionei alguns novos métodos que não eram possíveis no Python2, como list.copy()(o equivalente à fatia do Python3 ) e duas formas de descompactar a lista ( *new_list, = liste 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 o vencedor do Python2 ainda se sai bem, mas não supera list.copy()muito o Python3 , principalmente considerando a legibilidade superior deste último.

O azarão é o método de desempacotamento e reembalagem ( b = [*a]), que é ~ 25% mais rápido que o fatiamento bruto e mais do que o dobro do outro método de desempacotamento ( *b, = a).

b = a * 1 também faz surpreendentemente bem.

Observe que esses métodos não produzem resultados equivalentes para nenhuma entrada que não seja listas. Todos eles trabalham para objetos sliceable, alguns funcionam para qualquer iterável, mas copy.copy()funcionam apenas para objetos Python mais gerais.


Aqui está o código de teste para as partes interessadas ( modelo daqui ):

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))
Rio
fonte
1
Ainda é possível confirmar uma história semelhante em 3.8 b=[*a]- a única maneira óbvia de fazê-lo;).
SuperShoot
20

Todos os outros colaboradores deram ótimas respostas, que funcionam quando você tem uma lista de dimensão única (nivelada), no entanto, dos métodos mencionados até agora, apenas copy.deepcopy()funcionam para clonar / copiar uma lista e não apontar para os listobjetos aninhados quando você está trabalhando com listas aninhadas multidimensionais (lista de listas). Embora Felix Kling se refira a isso em sua resposta, há um pouco mais sobre o problema e possivelmente uma solução alternativa usando os built-ins que podem ser uma alternativa mais rápida deepcopy.

Enquanto new_list = old_list[:], copy.copy(old_list)'e para Py3k old_list.copy()trabalho para listas individuais nivelado, eles reverter para apontando para os listobjetos aninhados dentro do old_liste do new_list, e as mudanças a um doslist objetos são perpetuadas no outro.

Edit: Novas informações trazidas à luz

Como foi apontado por Aaron Hall e PM 2Ring, o uso eval()não é apenas uma má ideia, é também muito mais lento que copy.deepcopy().

Isso significa que, para listas multidimensionais, a única opção é copy.deepcopy(). Com isso dito, realmente não é uma opção, pois o desempenho vai para o sul quando você tenta usá-lo em uma matriz multidimensional de tamanho moderado. Eu tenteitimeit usar uma matriz de 42x42, que não era desconhecida ou grande demais para aplicativos de bioinformática, e desisti de esperar por uma resposta e comecei a digitar minha edição nesta postagem.

Parece que a única opção real é inicializar várias listas e trabalhar nelas independentemente. Se alguém tiver outras sugestões, sobre como lidar com a cópia de lista multidimensional, isso será apreciado.

Como outros já declararam, há problemas significativos de desempenho usando o copymódulo e copy.deepcopy para listas multidimensionais .

AMR
fonte
5
Isso nem sempre funciona, pois não há garantia de que a sequência retornada por repr()seja suficiente para recriar o objeto. Além disso, eval()é uma ferramenta de último recurso; veja Eval é realmente perigoso pelo veterano do SO Ned Batchelder para obter detalhes. Então, quando você defendem o uso eval()que você realmente deve mencionar que ele pode ser perigoso.
PM 2Ring
1
Ponto justo. Embora eu ache que o argumento de Batchelder é que ter a eval()função em Python em geral é um risco. Não é tanto se você usa ou não a função no código, mas é uma falha de segurança no Python por si só. Meu exemplo não está a usá-lo com uma função que recebe entrada de input(), sys.agrvou mesmo um arquivo de texto. É mais parecido com a inicialização de uma lista multidimensional em branco uma vez e apenas com uma maneira de copiá-la em um loop, em vez de reinicializar a cada iteração do loop.
AMR10:
1
Como o @AaronHall apontou, é provável que haja um problema de desempenho significativo no uso new_list = eval(repr(old_list)), portanto, além de ser uma má idéia, provavelmente também é muito lento para trabalhar.
AMR
13

Surpreende-me que isso ainda não tenha sido mencionado, por uma questão de completude ...

Você pode descompactar a lista com o "operador de splat":, *que também copiará elementos da sua 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]

A desvantagem óbvia desse método é que ele está disponível apenas no Python 3.5+.

Em termos de tempo, porém, isso parece ter um desempenho melhor do que outros métodos comuns.

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
fonte
1
Como esse método se comporta ao modificar cópias?
precisa saber é
2
@ not2qubit, você quer anexar ou editar elementos da nova lista. No exemplo old_liste new_listexistem duas listas diferentes, a edição de uma não mudará a outra (a menos que você esteja mutando diretamente os elementos em si (como a lista de lista), nenhum desses métodos é uma cópia profunda).
SCB 25/09
8

Uma abordagem muito simples, independente da versão python, estava ausente nas respostas já fornecidas, que você pode usar na maioria das vezes (pelo menos eu faço):

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

No entanto, se my_list contiver outros contêineres (por exemplo, listas aninhadas), você deverá usar cópia em profundidade, conforme sugerido nas respostas acima da biblioteca de cópias. Por exemplo:

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

. Bônus : Se você não deseja copiar elementos, use (também conhecido como cópia superficial):

new_list = my_list[:]

Vamos entender a diferença entre a solução 1 e a solução 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 você pode ver, a solução nº 1 funcionou perfeitamente quando não estávamos usando as listas aninhadas. Vamos verificar o que acontecerá quando aplicarmos a solução 1 às listas aninhadas.

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

Observe que existem alguns casos em que se você definiu sua própria classe personalizada e deseja manter os atributos, deve usar copy.copy()ou copy.deepcopy()não as alternativas, por exemplo, no 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))

Saídas:

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

new_list = my_list Tente entender isso. Digamos que my_list esteja na memória da pilha no local X, ou seja, my_list está apontando para o X. Agora, atribuindo new_list = my_listvocê está Letting new_list apontando para o X. Isso é conhecido como cópia superficial.

Agora, se você atribuir, new_list = my_list[:]você está simplesmente copiando cada objeto da minha lista para a nova lista. Isso é conhecido como cópia profunda.

A outra maneira de fazer isso é:

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

Eu queria postar algo um pouco diferente do que algumas das outras respostas. Embora essa provavelmente não seja a opção mais compreensível ou mais rápida, ela fornece uma visão interna de como funciona a cópia em profundidade, além de ser outra opção alternativa para cópia em profundidade. Realmente não importa se minha função possui bugs, pois o objetivo é mostrar uma maneira de copiar objetos como as respostas da pergunta, mas também usá-lo como um ponto para explicar como a cópia profunda funciona em seu núcleo.

No centro de qualquer função de cópia profunda, há uma maneira de fazer uma cópia superficial. Quão? Simples. Qualquer função de cópia profunda apenas duplica os contêineres de objetos imutáveis. Ao copiar em profundidade uma lista aninhada, você está duplicando apenas as listas externas, não os objetos mutáveis ​​dentro das listas. Você está apenas duplicando os contêineres. O mesmo vale para as aulas também. Quando você copia uma classe em profundidade, copia todos os seus atributos mutáveis. Então como? Como você só precisa copiar os contêineres, como listas, dictos, tuplas, iteradores, classes e instâncias de classe?

É simples. Um objeto mutável não pode realmente ser duplicado. Ele nunca pode ser alterado, portanto, é apenas um valor único. Isso significa que você nunca precisa duplicar strings, números, bools ou qualquer um deles. Mas como você duplicaria os contêineres? Simples. Você apenas inicializa um novo contêiner com todos os valores. A cópia em profundidade depende da recursão. Duplica todos os contêineres, mesmo aqueles com contêineres dentro deles, até que nenhum contêiner seja deixado. Um contêiner é um objeto imutável.

Depois que você souber disso, duplicar completamente um objeto sem nenhuma referência é muito fácil. Aqui está uma função para copiar profundamente tipos de dados básicos (não funcionaria para classes personalizadas, mas você sempre pode adicioná-lo)

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

A copia profunda interna do Python é baseada nesse exemplo. A única diferença é que ele suporta outros tipos e também suporta classes de usuários duplicando os atributos em uma nova classe duplicada e também bloqueia a recursão infinita com uma referência a um objeto que já é visto usando uma lista ou dicionário de notas. E é isso mesmo para fazer cópias profundas. No fundo, fazer uma cópia profunda é apenas fazer cópias rasas. Espero que esta resposta acrescente algo à pergunta.

EXEMPLOS

Digamos que você tenha esta lista: [1, 2, 3] . Os números imutáveis ​​não podem ser duplicados, mas a outra camada pode. Você pode duplicar usando uma compreensão de lista: [x para x em [1, 2, 3]

Agora, imagine que você tenha esta lista: [[1, 2], [3, 4], [5, 6]] . Desta vez, você deseja criar uma função, que usa recursão para copiar em profundidade todas as camadas da lista. Em vez da compreensão da lista anterior:

[x for x in _list]

Ele usa um novo para listas:

[deepcopy_list(x) for x in _list]

E deepcopy_list fica assim:

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

Agora você tem uma função que pode fazer uma cópia profunda de qualquer lista de strs, bools, floast, ints e até listas para infinitas camadas usando recursão. E aí está, cópia em profundidade.

TLDR : o Deepcopy usa recursão para duplicar objetos e simplesmente retorna os mesmos objetos imutáveis ​​de antes, pois os objetos imutáveis ​​não podem ser duplicados. No entanto, copia em profundidade as camadas mais internas dos objetos mutáveis ​​até atingir a camada mutável mais externa de um objeto.

Corman
fonte
3

Uma pequena perspectiva prática para examinar a memória através de id e 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.
fonte
3

Lembre-se disso no Python quando você faz:

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

Lista2 não está armazenando a lista real, mas uma referência à lista1. Portanto, quando você faz qualquer coisa na lista1, a lista2 também muda. use o módulo de cópia (não padrão, faça o download no pip) para fazer uma cópia original da lista ( copy.copy()para listas simples, copy.deepcopy()para aninhadas). Isso faz uma cópia que não muda com a primeira lista.

Dr. Hippo
fonte
1

A opção deepcopy é o único método que funciona para mim:

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

leva à saída 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
fonte
1

Isso ocorre porque a linha new_list = my_listatribui uma nova referência à variável my_listque new_list é semelhante ao Ccódigo fornecido abaixo,

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

Você deve usar o módulo de cópia para criar uma nova lista

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