Question Comment cloner ou copier une liste?


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

En utilisant new_list = my_list puis modifie new_list à chaque fois my_list changements.
Pourquoi est-ce?


1690
2018-04-10 08:49


origine


Réponses:


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

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

  • Vous pouvez utiliser le builtin list.copy() méthode (disponible depuis python 3.3):

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

    new_list = old_list[:]
    

    Alex Martelli opinion (au moins retour en 2007) à ce sujet, que c'est une syntaxe bizarre et ça n'a pas de sens de l'utiliser. ;) (Selon lui, le prochain est plus lisible).

  • Vous pouvez utiliser le construit dans list() fonction:

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

    import copy
    new_list = copy.copy(old_list)
    

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

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

    import copy
    new_list = copy.deepcopy(old_list)
    

    De toute évidence, 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 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))

Résultat:

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



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

  1. 10.59 sec (105.9us / itn) - copy.deepcopy(old_list)
  2. 10.16 sec (101.6us / itn) - python pur Copy() méthode copiant des classes avec deepcopy
  3. 1.488 sec (14.88us / itn) - python pur Copy() méthode ne copiant pas les classes (seulement les dicts / 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 liste de compréhension)
  6. 0.186 sec (1.86us / itn) - copy.copy(old_list)
  7. 0.075 sec (0.75us / itn) - list(old_list)
  8. 0.053 sec (0.53us / itn) - new_list = []; new_list.extend(old_list)
  9. 0.039 sec (0.39us / itn) - old_list[:] (liste tranchage)

Le plus rapide est donc le découpage en liste. Mais sachez que copy.copy(), list[:] et list(list), contrairement à copy.deepcopy() et la version python ne copie pas les listes, les dictionnaires et les 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 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

MODIFIER: Ajout de classes et de dictes de style ancien, de style ancien aux repères, et rendu la version python beaucoup plus rapide et ajouté quelques méthodes supplémentaires, y compris des expressions de liste et extend().


447
2018-04-10 10:16



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

newlist = old_list.copy()


116
2017-07-23 12:32



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 y a deux façons sémantiques de copier une liste. Une copie superficielle crée une nouvelle liste des mêmes objets, une copie profonde 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 contenus sont mutables et que l'un d'eux est modifié, le changement sera reflété dans les deux listes.

Il y a différentes manières de faire ceci dans Python 2 et 3. Les 2 manières de Python fonctionneront également dans Python 3.

Python 2

Dans Python 2, la manière 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 à travers le constructeur de la liste,

a_copy = list(a_list)

mais en utilisant le 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 list.copy mé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 pas faire une copie

En utilisant new_list = my_list, puis modifie new_list chaque fois que my_list change. Pourquoi est-ce?

my_list est juste un nom qui pointe vers la liste actuelle en mémoire. Quand tu dis new_list = my_list vous ne faites pas de copie, vous ajoutez simplement un autre nom qui pointe vers cette liste originale en mémoire. Nous pouvons avoir 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 est juste un tableau de pointeurs vers le contenu, donc une copie superficielle copie juste les pointeurs, et ainsi vous avez deux listes différentes, mais elles ont le même contenu. Pour faire des copies du contenu, vous avez besoin d'une copie profonde.

Copies profondes

Faire un copie profonde d'une liste, en Python 2 ou 3, utilisez deepcopy dans le copy module:

import copy
a_deep_copy = copy.deepcopy(a_list)

Pour dé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 donc nous voyons 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 risquez de créer des bogues que vous n'auriez pas autrement en utilisant la fonction deepcopy de la bibliothèque standard.

Ne pas utiliser eval

Vous pouvez voir cela comme un moyen de deepcopy, mais ne le faites pas:

problematic_deep_copy = eval(repr(a_list))
  1. C'est dangereux, surtout si vous évaluez quelque chose d'une source à 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 puisse être évaluée pour reproduire un élément équivalent.
  3. C'est aussi moins performant.

En 64 bits 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

sur 64 bits Python 3.5:

>>> import timeit
>>> import copy
>>> l = list(range(10))
>>> min(timeit.repeat(lambda: copy.deepcopy(l)))
16.84255409205798
>>> min(timeit.repeat(lambda: eval(repr(l))))
34.813894678023644

87
2017-10-25 12:13



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

Python ne stocke pas les valeurs dans les variables; il lie les noms aux objets. Votre mission d'origine a pris l'objet visé par my_list et lié à new_list ainsi que. Quel que soit le nom que vous utilisez, il n'y a qu'une seule liste, donc des modifications sont faites en my_list persistera en se référant à lui comme new_list. Chacune des autres réponses à cette question vous donne différentes façons de créer un nouvel objet à 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'avant.

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 prendre votre copie de liste un peu plus loin, 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 profonde, 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 de manière récursive chaque élément de la liste, puis chaque autre objet référencé par chaque élément, et ainsi de suite: effectuer une copie en profondeur.

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

Voir La documentation pour plus d'informations sur les cas de coin dans la copie.


42
2017-11-23 16:45



new_list = list(old_list)


30
2018-04-10 09:03



Utilisation thing[:]

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

27
2018-04-10 08:53



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


26
2018-04-10 08:53