Comment cloner ou copier une liste?

2553

Quelles sont les options pour cloner ou copier une liste en Python?

Lors de l'utilisation new_list = my_list, toutes les modifications new_listapportées à my_listchaque fois. Pourquoi est-ce?

un F.
la source

Réponses:

3332

Avec new_list = my_list, vous n'avez pas réellement deux listes. L'affectation copie simplement la référence à la liste, pas la liste réelle, donc les deux new_listetmy_list faites référence à la même liste après l'affectation.

Pour copier réellement la liste, vous avez différentes possibilités:

  • Vous pouvez utiliser la list.copy()méthode intégrée (disponible depuis Python 3.3):

    new_list = old_list.copy()
  • Vous pouvez le découper:

    new_list = old_list[:]

    L' opinion d' Alex Martelli (au moins en 2007 ) à ce sujet est que c'est une syntaxe étrange et qu'il n'est pas logique de l'utiliser jamais . ;) (À son avis, le suivant est plus lisible).

  • Vous pouvez utiliser la list()fonction intégrée:

    new_list = list(old_list)
  • Vous pouvez utiliser des génériques copy.copy():

    import copy
    new_list = copy.copy(old_list)

    C'est un peu plus lent que list()parce qu'il faut d'abord trouver le type de données old_list.

  • Si la liste contient des objets et que vous souhaitez également les copier, utilisez générique copy.deepcopy():

    import copy
    new_list = copy.deepcopy(old_list)

    Évidemment la méthode la plus lente et la plus gourmande en mémoire, mais parfois inévitable.

Exemple:

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

Résultat:

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
la source
7
Si je ne me trompe pas: newlist = [*mylist]il existe également une possibilité en Python 3. newlist = list(mylist)est peut-être plus clair cependant.
Stéphane
9
une autre possibilité est new_list = old_list * 1
aris
4
Laquelle de ces méthodes est une copie superficielle et laquelle est une copie approfondie?
Eswar
4
@Eswar: tous sauf le dernier font une copie superficielle
Felix Kling
3
@Eswar c'est une copie superficielle.
juanpa.arrivillaga
604

Felix a déjà fourni une excellente réponse, mais j'ai pensé que je ferais une comparaison rapide des différentes méthodes:

  1. 10,59 s (105,9us / itn) - copy.deepcopy(old_list)
  2. 10,16 sec (101.6us / itn) - python pur Copy() classes de copie de méthode avec copie profonde
  3. 1,488 sec (14.88us / itn) - python pur Copy() méthode ne copiant pas les classes (seulement les dict / listes / tuples)
  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](une compréhension de liste )
  6. 0,186 s (1,86 us / 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)
  9. 0,039 sec (0,39us / itn) - old_list[:]( liste de découpage )

Le découpage de liste est donc le plus rapide. Mais sachez que copy.copy(), list[:]et list(list)contrairement à copy.deepcopy()la version python, ne copiez pas de listes, de dictionnaires et d'instances de classe dans la liste, donc si les originaux changent, ils changeront également dans la liste copiée et vice versa.

(Voici le script si quelqu'un est intéressé ou souhaite soulever des problèmes :)

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
cryo
la source
9
Étant donné que vous effectuez une analyse comparative, il peut être utile d'inclure un point de référence. Ces chiffres sont-ils toujours exacts en 2017 en utilisant Python 3.6 avec du code entièrement compilé? Je note la réponse ci-dessous ( stackoverflow.com/a/17810305/26219 ) interroge déjà cette réponse.
Mark Edington
4
utilisez le timeitmodule. en outre, vous ne pouvez pas conclure grand-chose de micro-repères arbitraires comme celui-ci.
Corey Goldberg
3
Si vous souhaitez inclure une nouvelle option pour 3.5+, [*old_list]devrait être à peu près équivalente à list(old_list), mais comme c'est la syntaxe, et non les chemins d'appels de fonction généraux, cela économisera un peu sur l'exécution (et contrairement à old_list[:], qui ne saisit pas convert, [*old_list]fonctionne sur tout itérable et produit a list).
ShadowRanger
3
@CoreyGoldberg pour un micro-benchmark un peu moins arbitraire (utilise timeit, 50m s'exécute au lieu de 100k) voir stackoverflow.com/a/43220129/3745896
River
1
@ShadowRanger [*old_list]semble en fait surpasser presque toutes les autres méthodes. (voir ma réponse liée dans les commentaires précédents)
Rivière
151

J'ai été dit que Python 3.3+ ajoutelist.copy() méthode, qui devrait être aussi rapide que le découpage:

newlist = old_list.copy()

anatoly techtonik
la source
6
Oui, et selon les documents docs.python.org/3/library/stdtypes.html#mutable-sequence-types , s.copy()crée une copie superficielle de s(identique à s[:]).
CyberMew
En fait, il semble qu'actuellement python3.8, .copy()soit légèrement plus rapide que le tranchage. Voir ci-dessous la réponse @AaronsHall.
loves.by.Jesus
126

Quelles sont les options pour cloner ou copier une liste en Python?

En Python 3, une copie superficielle peut être faite avec:

a_copy = a_list.copy()

En Python 2 et 3, vous pouvez obtenir une copie superficielle avec une tranche complète de l'original:

a_copy = a_list[:]

Explication

Il existe deux façons sémantiques de copier une liste. Une copie superficielle crée une nouvelle liste des mêmes objets, une copie complète crée une nouvelle liste contenant de nouveaux objets équivalents.

Copie de liste peu profonde

Une copie superficielle copie uniquement la liste elle-même, qui est un conteneur de références aux objets de la liste. Si les objets qu'ils contiennent sont modifiables et que l'un est modifié, le changement sera reflété dans les deux listes.

Il existe différentes façons de le faire en Python 2 et 3. Les méthodes Python 2 fonctionneront également en Python 3.

Python 2

En Python 2, la façon idiomatique de faire une copie superficielle d'une liste est avec une tranche complète de l'original:

a_copy = a_list[:]

Vous pouvez également accomplir la même chose en passant la liste via le constructeur de liste,

a_copy = list(a_list)

mais l'utilisation du constructeur est moins efficace:

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

Python 3

En Python 3, les listes obtiennent la list.copyméthode:

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

Faire un autre pointeur ne fait pas de copie

Utiliser new_list = my_list modifie ensuite new_list à chaque fois que my_list change. Pourquoi est-ce?

my_listest juste un nom qui pointe vers la liste réelle en mémoire. Lorsque vous dites new_list = my_listque vous ne faites pas de copie, vous ajoutez simplement un autre nom qui pointe vers cette liste d'origine en mémoire. Nous pouvons rencontrer des problèmes similaires lorsque nous faisons des copies de listes.

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

La liste n'est qu'un tableau de pointeurs vers le contenu, donc une copie superficielle copie simplement les pointeurs, et vous avez donc deux listes différentes, mais elles ont le même contenu. Pour faire des copies du contenu, vous avez besoin d'une copie complète.

Copies complètes

Pour faire une copie complète d'une liste, en Python 2 ou 3, utilisez deepcopydans le copymodule :

import copy
a_deep_copy = copy.deepcopy(a_list)

Pour montrer comment cela nous permet de créer de nouvelles sous-listes:

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

Et nous voyons donc que la liste copiée en profondeur est une liste entièrement différente de l'original. Vous pouvez lancer votre propre fonction - mais ne le faites pas. Vous êtes susceptible de créer des bogues que vous n'auriez pas autrement en utilisant la fonction de copie profonde de la bibliothèque standard.

Ne pas utiliser eval

Vous pouvez voir cela comme un moyen de réaliser une copie profonde, mais ne le faites pas:

problematic_deep_copy = eval(repr(a_list))
  1. C'est dangereux, surtout si vous évaluez quelque chose à partir d'une source en laquelle vous n'avez pas confiance.
  2. Ce n'est pas fiable, si un sous-élément que vous copiez n'a pas de représentation qui peut être évaluée pour reproduire un élément équivalent.
  3. C'est aussi moins performant.

En Python 2.7 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

sur Python 3.5 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
la source
1
Vous n'avez pas besoin d'une copie profonde si la liste est en 2D. S'il s'agit d'une liste de listes et que ces listes ne contiennent pas de listes, vous pouvez utiliser une boucle for. Actuellement, j'utilise list_copy=[] for item in list: list_copy.append(copy(item))et c'est beaucoup plus rapide.
John Locke
54

Il existe déjà de nombreuses réponses qui vous indiquent comment faire une copie correcte, mais aucune d'entre elles ne dit pourquoi votre copie originale a échoué.

Python ne stocke pas de valeurs dans des variables; il lie des noms aux objets. Votre mission d'origine a pris l'objet auquel il était fait référence my_listet l'a également lié new_list. Peu importe le nom que vous utilisez, il n'y a toujours qu'une seule liste, donc les modifications apportées lorsque vous vous my_listy référez comme persisteront lorsque vous vous y référer en tant que new_list. Chacune des autres réponses à cette question vous donne différentes façons de créer un nouvel objet auquel vous lier new_list.

Chaque élément d'une liste agit comme un nom, en ce sens que chaque élément se lie non exclusivement à un objet. Une copie superficielle crée une nouvelle liste dont les éléments se lient aux mêmes objets qu'auparavant.

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]

Pour aller plus loin dans votre liste, copiez chaque objet auquel votre liste fait référence et liez ces copies d'élément à une nouvelle liste.

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

Ce n'est pas encore une copie complète, car chaque élément d'une liste peut faire référence à d'autres objets, tout comme la liste est liée à ses éléments. Pour copier récursivement chaque élément de la liste, puis chaque autre objet référencé par chaque élément, etc.: effectuez une copie complète.

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

Consultez la documentation pour plus d'informations sur les cas d'angle lors de la copie.

jack
la source
38

Utilisation thing[:]

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

Commençons par le début et explorons cette question.

Supposons donc que vous ayez deux listes:

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

Et nous devons copier les deux listes, en commençant maintenant par la première liste:

Essayons donc d'abord en définissant la variable copysur notre liste d'origine list_1:

copy=list_1

Maintenant, si vous pensez copier copié la liste_1, alors vous vous trompez. La idfonction peut nous montrer si deux variables peuvent pointer vers le même objet. Essayons ça:

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

La sortie est:

4329485320
4329485320

Les deux variables sont exactement le même argument. Êtes-vous surpris?

Donc, comme nous savons que python ne stocke rien dans une variable, les variables font simplement référence à l'objet et l'objet stocke la valeur. Ici l'objet est unlist mais nous avons créé deux références à ce même objet par deux noms de variables différents. Cela signifie que les deux variables pointent vers le même objet, juste avec des noms différents.

Lorsque vous le faites copy=list_1, cela signifie en fait:

entrez la description de l'image ici

Ici, dans la liste d'images_1 et la copie sont deux noms de variables, mais l'objet est le même pour les deux variables qui est list

Donc, si vous essayez de modifier la liste copiée, cela modifiera également la liste d'origine, car la liste n'en est qu'une, vous modifierez cette liste, peu importe ce que vous faites à partir de la liste copiée ou de la liste d'origine:

copy[0]="modify"

print(copy)
print(list_1)

production:

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

Il a donc modifié la liste d'origine:

Passons maintenant à une méthode pythonique pour copier des listes.

copy_1=list_1[:]

Cette méthode corrige le premier problème rencontré:

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

4338792136
4338791432

Donc, comme nous pouvons voir nos deux listes ayant un identifiant différent et cela signifie que les deux variables pointent vers des objets différents. Donc, ce qui se passe réellement ici, c'est:

entrez la description de l'image ici

Essayons maintenant de modifier la liste et voyons si nous sommes toujours confrontés au problème précédent:

copy_1[0]="modify"

print(list_1)
print(copy_1)

La sortie est:

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

Comme vous pouvez le voir, il n'a modifié que la liste copiée. Cela signifie que cela a fonctionné.

Pensez-vous que nous avons terminé? Essayons de copier notre liste imbriquée.

copy_2=list_2[:]

list_2doit faire référence à un autre objet qui est une copie de list_2. Allons vérifier:

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

Nous obtenons la sortie:

4330403592 4330403528

Maintenant, nous pouvons supposer que les deux listes pointent sur un objet différent, alors essayons maintenant de le modifier et voyons qu'il donne ce que nous voulons:

copy_2[0][1]="modify"

print(list_2,copy_2)

Cela nous donne la sortie:

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

Cela peut sembler un peu déroutant, car la même méthode que celle utilisée précédemment a fonctionné. Essayons de comprendre cela.

Quand vous faites:

copy_2=list_2[:]

Vous copiez uniquement la liste externe, pas la liste interne. Nous pouvons à nouveau utiliser la idfonction pour vérifier cela.

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

La sortie est:

4329485832
4329485832

Lorsque nous le faisons copy_2=list_2[:], cela se produit:

entrez la description de l'image ici

Il crée la copie de la liste mais uniquement la copie de la liste externe, pas la copie de la liste imbriquée, la liste imbriquée est la même pour les deux variables, donc si vous essayez de modifier la liste imbriquée, elle modifiera également la liste d'origine car l'objet de la liste imbriquée est le même pour les deux listes.

Quelle est la solution? La solution est la deepcopyfonction.

from copy import deepcopy
deep=deepcopy(list_2)

Vérifions ceci:

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

4322146056 4322148040

Les deux listes externes ont des ID différents, essayons ceci sur les listes imbriquées internes.

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

La sortie est:

4322145992
4322145800

Comme vous pouvez le voir, les deux ID sont différents, ce qui signifie que nous pouvons supposer que les deux listes imbriquées pointent maintenant vers un objet différent.

Cela signifie que lorsque vous faites deep=deepcopy(list_2)ce qui se passe réellement:

entrez la description de l'image ici

Les deux listes imbriquées pointent sur un objet différent et ont maintenant une copie distincte de la liste imbriquée.

Essayons maintenant de modifier la liste imbriquée et de voir si elle a résolu le problème précédent ou non:

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

Il génère:

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

Comme vous pouvez le voir, il n'a pas modifié la liste imbriquée d'origine, il a uniquement modifié la liste copiée.

Aaditya Ura
la source
34

L'idiome de Python pour ce faire est newList = oldList[:]

erisco
la source
34

Python 3.6 Timings

Voici les résultats de synchronisation avec Python 3.6.8. Gardez à l'esprit que ces temps sont relatifs et non absolus.

Je me suis contenté de ne faire que des copies superficielles, et j'ai également ajouté de nouvelles méthodes qui n'étaient pas possibles en Python2, comme list.copy()(l' équivalent de tranche Python3 ) et deux formes de décompression de liste ( *new_list, = listet 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

Nous pouvons voir que le gagnant de Python2 fonctionne toujours bien, mais ne dépasse pas list.copy()beaucoup Python3 , surtout compte tenu de la lisibilité supérieure de ce dernier.

Le cheval noir est la méthode de déballage et de reconditionnement ( b = [*a]), qui est ~ 25% plus rapide que le tranchage brut, et plus de deux fois plus rapide que l'autre méthode de déballage ( *b, = a).

b = a * 1 fait aussi étonnamment bien.

Notez que ces méthodes ne génèrent pas de résultats équivalents pour toute entrée autre que des listes. Ils fonctionnent tous pour les objets à découper, quelques-uns fonctionnent pour tout objet itérable, mais ne copy.copy()fonctionnent que pour les objets Python plus généraux.


Voici le code de test pour les parties intéressées ( modèle à partir d'ici ):

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))
rivière
la source
1
Peut confirmer encore une histoire similaire sur 3.8 b=[*a]- la seule façon évidente de le faire;).
SuperShoot
20

Tous les autres contributeurs ont donné d' excellentes réponses, qui fonctionnent lorsque vous avez une liste à une seule dimension (mise à niveau), mais les méthodes mentionnées jusqu'à présent ne copy.deepcopy()fonctionnent que pour cloner / copier une liste et ne pas la faire pointer vers les listobjets imbriqués lorsque vous êtes travailler avec des listes imbriquées multidimensionnelles (liste de listes). Alors que Felix Kling y fait référence dans sa réponse, il y a un peu plus sur le problème et peut-être une solution de contournement utilisant des intégrés qui pourrait s'avérer une alternative plus rapide deepcopy.

Tandis que new_list = old_list[:], copy.copy(old_list)'et pour Py3k, old_list.copy()fonctionnent pour les listes à un seul niveau, ils reviennent à pointer sur les listobjets imbriqués dans le old_listet le new_list, et les modifications apportées à l'un des listobjets sont perpétuées dans l'autre.

Edit: De nouvelles informations mises à jour

Comme l'ont souligné Aaron Hall et PM 2Ring, l' utilisation eval()n'est pas seulement une mauvaise idée, elle est également beaucoup plus lente copy.deepcopy().

Cela signifie que pour les listes multidimensionnelles, la seule option est copy.deepcopy(). Cela étant dit, ce n'est vraiment pas une option car les performances vont bien au sud lorsque vous essayez de les utiliser sur un tableau multidimensionnel de taille modérée. j'ai essayé detimeit utiliser un tableau 42x42, pas inconnu ou même si grand pour les applications bioinformatiques, et j'ai renoncé à attendre une réponse et j'ai juste commencé à taper ma modification à ce poste.

Il semblerait que la seule véritable option consiste alors à initialiser plusieurs listes et à les travailler indépendamment. Si quelqu'un a d'autres suggestions sur la façon de gérer la copie de liste multidimensionnelle, ce serait apprécié.

Comme d'autres l'ont indiqué, il existe des problèmes de performances importants en utilisant le copymodule et copy.deepcopy pour les listes multidimensionnelles .

AMR
la source
5
Cela ne fonctionnera pas toujours, car il n'y a aucune garantie que la chaîne renvoyée par repr()est suffisante pour recréer l'objet. C'est aussi eval()un outil de dernier recours; voir Eval est vraiment dangereux par le vétéran de SO Ned Batchelder pour plus de détails. Donc, lorsque vous préconisez l'utilisation, eval()vous devez vraiment mentionner que cela peut être dangereux.
PM 2Ring
1
Bon point. Bien que je pense que le point de Batchelder est que le fait d'avoir la eval()fonction en Python en général est un risque. Ce n'est pas tant si vous utilisez ou non la fonction dans le code que c'est un trou de sécurité dans Python en soi. Mon exemple n'utilise pas avec une fonction qui reçoit une entrée de input(), sys.agrvou même un fichier texte. Il s'agit plutôt d'initialiser une fois une liste multidimensionnelle vierge, puis de simplement la copier dans une boucle au lieu de la réinitialiser à chaque itération de la boucle.
AMR
1
Comme l'a souligné @AaronHall, il y a probablement un problème de performance important à utiliser new_list = eval(repr(old_list)), donc en plus d'être une mauvaise idée, il est probablement beaucoup trop lent pour fonctionner.
AMR
13

Cela m'étonne que cela n'ait pas encore été mentionné, donc pour être complet ...

Vous pouvez effectuer le déballage de la liste avec "l'opérateur splat":, *qui copiera également les éléments de votre liste.

old_list = [1, 2, 3]

new_list = [*old_list]

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

L'inconvénient évident de cette méthode est qu'elle n'est disponible qu'en Python 3.5+.

Cependant, en termes de calendrier, cela semble fonctionner mieux que d'autres méthodes courantes.

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
la source
1
Comment cette méthode se comporte-t-elle lors de la modification des copies?
not2qubit
2
@ not2qubit, voulez-vous dire ajouter ou modifier des éléments de la nouvelle liste. Dans l'exemple old_listet il new_listy a deux listes différentes, l'édition de l'une ne changera pas l'autre (à moins que vous ne mutiez directement les éléments eux-mêmes (comme la liste de liste), aucune de ces méthodes n'est une copie complète).
SCB
8

Une approche très simple indépendante de la version python manquait dans les réponses déjà données que vous pouvez utiliser la plupart du temps (du moins je le fais):

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

Cependant, si ma_liste contient d'autres conteneurs (par exemple des listes imbriquées), vous devez utiliser la copie profonde comme d'autres l'ont suggéré dans les réponses ci-dessus à partir de la bibliothèque de copie. Par exemple:

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

. Bonus : Si vous ne voulez pas copier les éléments, utilisez (aka copie superficielle):

new_list = my_list[:]

Comprenons la différence entre la solution # 1 et la solution # 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])

Comme vous pouvez le voir, la solution n ° 1 a parfaitement fonctionné lorsque nous n'utilisions pas les listes imbriquées. Voyons ce qui se passera lorsque nous appliquerons la solution # 1 aux listes imbriquées.

>>> 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
la source
8

Notez que dans certains cas, si vous avez défini votre propre classe personnalisée et que vous souhaitez conserver les attributs, vous devez utiliser copy.copy()ou copy.deepcopy()plutôt que les alternatives, par exemple 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))

Les sorties:

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

new_list = my_list Essayez de comprendre cela. Disons que ma_liste est dans la mémoire du tas à l'emplacement X, c'est-à-dire que ma_liste pointe vers le X. Maintenant, en affectantnew_list = my_list vous laissez new_list pointer vers le X. C'est ce que l'on appelle la copie superficielle.

Maintenant, si vous affectez, new_list = my_list[:]vous copiez simplement chaque objet de my_list vers new_list. Ceci est connu sous le nom de copie profonde.

L'autre façon de procéder est la suivante:

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

Je voulais poster quelque chose d'un peu différent de certaines des autres réponses. Même si ce n'est probablement pas l'option la plus compréhensible ou la plus rapide, elle fournit un aperçu de la façon dont fonctionne la copie en profondeur, tout en étant une autre option alternative pour la copie en profondeur. Peu importe si ma fonction a des bogues, car cela a pour but de montrer un moyen de copier des objets comme les réponses aux questions, mais aussi de l'utiliser comme un point pour expliquer comment fonctionne la deepcopy à sa base.

Au cœur de toute fonction de copie profonde se trouve un moyen de faire une copie superficielle. Comment? Facile. Toute fonction de copie en profondeur ne fait que dupliquer les conteneurs d'objets immuables. Lorsque vous copiez en profondeur une liste imbriquée, vous dupliquez uniquement les listes externes, pas les objets modifiables à l'intérieur des listes. Vous dupliquez uniquement les conteneurs. La même chose fonctionne aussi pour les classes. Lorsque vous copiez en profondeur une classe, vous copiez en profondeur tous ses attributs mutables. Alors, comment? Comment se fait-il que vous n'ayez qu'à copier les conteneurs, comme les listes, les dict, les tuples, les iters, les classes et les instances de classe?

C'est simple. Un objet modifiable ne peut pas vraiment être dupliqué. Il ne peut jamais être modifié, il ne s'agit donc que d'une seule valeur. Cela signifie que vous n'avez jamais à dupliquer des chaînes, des nombres, des bools ou n'importe lequel d'entre eux. Mais comment dupliqueriez-vous les conteneurs? Facile. Vous faites simplement initialiser un nouveau conteneur avec toutes les valeurs. Deepcopy repose sur la récursivité. Il duplique tous les conteneurs, même ceux avec des conteneurs à l'intérieur, jusqu'à ce qu'il ne reste plus de conteneurs. Un conteneur est un objet immuable.

Une fois que vous le savez, la duplication complète d'un objet sans aucune référence est assez facile. Voici une fonction pour la copie en profondeur des types de données de base (ne fonctionnerait pas pour les classes personnalisées mais vous pouvez toujours l'ajouter)

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 copie profonde intégrée de Python est basée sur cet exemple. La seule différence est qu'il prend en charge d'autres types, et prend également en charge les classes d'utilisateurs en dupliquant les attributs dans une nouvelle classe en double, et bloque également la récursion infinie avec une référence à un objet qu'il a déjà vu à l'aide d'une liste de mémos ou d'un dictionnaire. Et c'est vraiment ça pour faire des copies profondes. À la base, faire une copie profonde est juste faire des copies superficielles. J'espère que cette réponse ajoute quelque chose à la question.

EXEMPLES

Disons que vous avez cette liste: [1, 2, 3] . Les nombres immuables ne peuvent pas être dupliqués, mais l'autre couche le peut. Vous pouvez le dupliquer en utilisant une liste de compréhension: [x pour x dans [1, 2, 3]

Maintenant, imaginez que vous avez cette liste: [[1, 2], [3, 4], [5, 6]] . Cette fois, vous voulez créer une fonction, qui utilise la récursivité pour copier en profondeur toutes les couches de la liste. Au lieu de la compréhension de la liste précédente:

[x for x in _list]

Il en utilise un nouveau pour les listes:

[deepcopy_list(x) for x in _list]

Et deepcopy_list ressemble à ceci:

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

Ensuite, vous avez maintenant une fonction qui peut copier en profondeur toute liste de chaînes, bools, floast, ints et même des listes sur une infinité de couches en utilisant la récursivité. Et voilà, la copie profonde.

TLDR : Deepcopy utilise la récursivité pour dupliquer des objets et renvoie simplement les mêmes objets immuables qu'avant, car les objets immuables ne peuvent pas être dupliqués. Cependant, il copie en profondeur les couches les plus internes d'objets mutables jusqu'à ce qu'il atteigne la couche la plus externe d'un objet.

Corman
la source
3

Une légère perspective pratique pour regarder dans la mémoire à travers id et 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.
la source
3

N'oubliez pas qu'en Python lorsque vous faites:

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

List2 ne stocke pas la liste réelle, mais une référence à list1. Ainsi, lorsque vous faites quoi que ce soit pour list1, list2 change également. utilisez le module de copie (pas par défaut, téléchargez sur pip) pour faire une copie originale de la liste ( copy.copy()pour les listes simples, copy.deepcopy()pour celles imbriquées). Cela fait une copie qui ne change pas avec la première liste.

Dr Hippo
la source
1

L'option de copie profonde est la seule méthode qui fonctionne pour moi:

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

conduit à la sortie 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
la source
1

En effet, la ligne new_list = my_listattribue une nouvelle référence à la variable my_listqui new_list est similaire au Ccode donné ci-dessous,

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

Vous devez utiliser le module de copie pour créer une nouvelle liste en

import copy
new_list = copy.deepcopy(my_list)
Roshin Raphel
la source