3205 votes

Comment cloner une liste pour qu'elle ne change pas de façon inattendue après son affectation ?

En utilisant new_list = my_list toute modification de new_list changements my_list à chaque fois. Pourquoi cela se produit-il, et comment puis-je cloner ou copier la liste pour éviter cela ?

5 votes

new_list = my_list attribue simplement le nom new_list à l'objet my_list se réfère à.

9 votes

Voir le FAQ Python .

3979voto

Felix Kling Points 247451

Con new_list = my_list vous n'avez pas réellement deux listes. L'affectation ne fait que copier la référence à la liste, et non la liste elle-même, de sorte que les deux listes de new_list y my_list se référer à la même liste après l'affectation.

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

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

    new_list = old_list.copy()
  • Vous pouvez le couper en tranches :

    new_list = old_list[:]

    Alex Martelli opinion (au moins en 2007 ) à ce sujet est que c'est une syntaxe bizarre et cela n'a pas de sens de l'utiliser jamais . ;) (A son avis, le suivant est plus lisible).

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

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

    import copy
    new_list = copy.copy(old_list)

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

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

    import copy
    new_list = copy.deepcopy(old_list)

    C'est évidemment la méthode la plus lente et la plus gourmande en mémoire, mais elle est parfois inévitable.

Ejemplo:

import copy

class Foo(object):
    def __init__(self, val):
         self.val = val

    def __repr__(self):
        return 'Foo({!r})'.format(self.val)

foo = Foo(1)

a = ['foo', foo]
b = a.copy()
c = a[:]
d = list(a)
e = copy.copy(a)
f = copy.deepcopy(a)

# edit orignal list and instance 
a.append('baz')
foo.val = 5

print('original: %r\nlist.copy(): %r\nslice: %r\nlist(): %r\ncopy: %r\ndeepcopy: %r'
      % (a, b, c, d, e, f))

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

6 votes

Comme le souligne correctement @Georgy dans la réponse ci-dessous, toute modification des valeurs de new_list modifiera également les valeurs de my_list. Donc, en fait, la méthode copy.deepcopy() est la seule véritable copie sans référence à la liste originale et à ses valeurs.

0 votes

@Erri Je pense que vous avez fait une erreur. Je n'ai pas posté de réponses ou de commentaires ici :)

0 votes

Vous avez raison, il a été édité par vous, mais posté par @cryo Désolé pour la confusion !

741voto

cryo Points 4773

Felix a déjà fourni une excellente réponse, mais j'ai pensé faire une comparaison de vitesse des différentes méthodes :

  1. 10.59 sec (105.9 µs/itn) - copy.deepcopy(old_list)
  2. 10,16 sec (101,6 µs/itn) - Python pur Copy() méthode de copie de classes avec deepcopy
  3. 1.488 sec (14.88 µs/itn) - Python pur Copy() méthode ne copiant pas les classes (seulement les dicts/listes/tuples)
  4. 0,325 sec (3,25 µs/itn) - for item in old_list: new_list.append(item)
  5. 0,217 sec (2,17 µs/itn) - [i for i in old_list] (a compréhension de la liste )
  6. 0,186 sec (1,86 µs/itn) - copy.copy(old_list)
  7. 0,075 sec (0,75 µs/itn) - list(old_list)
  8. 0,053 sec (0,53 µs/itn) - new_list = []; new_list.extend(old_list)
  9. 0,039 sec (0,39 µs/itn) - old_list[:] ( découpage de la liste )

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

(Voici le script si quelqu'un est intéressé ou veut soulever des questions :)

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

0 votes

Cela signifie-t-il que l'append et la compréhension de la liste sont les meilleures options ?

0 votes

J'ai un cache contenant une liste de classes, je veux prendre le verrou, copier la liste, libérer le verrou. J'espère qu'il est suffisant d'utiliser la copie intégrée pour protéger la liste copiée contre les modifications lorsque la copie du cache est modifiée.

0 votes

Je reviens sans cesse à cette réponse pour m'assurer que j'utilise la méthode la plus efficace. Quel est le moyen le plus simple de tester cette méthode ? Ou existe-t-il une base de données contenant toutes les meilleures méthodes pour minimiser le temps d'exécution ?

180voto

techtonik Points 2945

J'ai a été dit que Python 3.3+. ajoute le list.copy() qui devrait être aussi rapide que le découpage en tranches :

newlist = old_list.copy()

13 votes

Oui, et selon la documentation docs.python.org/3/library/stdtypes.html#mutable-sequence-types , s.copy() crée une copie superficielle de s (même chose que s[:] ).

4 votes

En fait, il semble que ce soit le cas actuellement, python3.8 , .copy() es légèrement plus rapide que le tranchage. Voir ci-dessous la réponse de @AaronsHall.

0 votes

@loved.by.Jesus : Ouais, ils ajout d'optimisations pour les appels de méthodes au niveau Python dans la version 3.7 qui ont été étendus à Appels de méthodes d'extension C en 3.8 par PEP 590 qui supprime la surcharge liée à la création d'une méthode liée à chaque fois que vous appelez une méthode, de sorte que le coût d'appel de la méthode alist.copy() est maintenant un dict sur le list puis un appel de fonction sans argument relativement bon marché qui, en fin de compte, invoque la même chose que le découpage en tranches. Le découpage doit encore construire un slice puis passer par les vérifications de type et le déballage pour faire la même chose.

54voto

Paul Tarjan Points 13754

Utilice thing[:]

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

36voto

user285176 Points 173

new_list = list(old_list)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X