Quelles sont les options pour cloner ou copier une liste en Python?
Lors de l'utilisation new_list = my_list
, toutes les modifications new_list
apportées à my_list
chaque fois. Pourquoi est-ce?
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_list
etmy_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)]
newlist = [*mylist]
il existe également une possibilité en Python 3.newlist = list(mylist)
est peut-être plus clair cependant.Felix a déjà fourni une excellente réponse, mais j'ai pensé que je ferais une comparaison rapide des différentes méthodes:
copy.deepcopy(old_list)
Copy()
classes de copie de méthode avec copie profondeCopy()
méthode ne copiant pas les classes (seulement les dict / listes / tuples)for item in old_list: new_list.append(item)
[i for i in old_list]
(une compréhension de liste )copy.copy(old_list)
list(old_list)
new_list = []; new_list.extend(old_list)
old_list[:]
( liste de découpage )Le découpage de liste est donc le plus rapide. Mais sachez que
copy.copy()
,list[:]
etlist(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 :)
la source
timeit
module. en outre, vous ne pouvez pas conclure grand-chose de micro-repères arbitraires comme celui-ci.[*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 alist
).timeit
, 50m s'exécute au lieu de 100k) voir stackoverflow.com/a/43220129/3745896[*old_list]
semble en fait surpasser presque toutes les autres méthodes. (voir ma réponse liée dans les commentaires précédents)J'ai été dit que Python 3.3+ ajoute
list.copy()
méthode, qui devrait être aussi rapide que le découpage:newlist = old_list.copy()
la source
s.copy()
crée une copie superficielle des
(identique às[:]
).python3.8
,.copy()
soit légèrement plus rapide que le tranchage. Voir ci-dessous la réponse @AaronsHall.En Python 3, une copie superficielle peut être faite avec:
En Python 2 et 3, vous pouvez obtenir une copie superficielle avec une tranche complète de l'original:
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:
Vous pouvez également accomplir la même chose en passant la liste via le constructeur de liste,
mais l'utilisation du constructeur est moins efficace:
Python 3
En Python 3, les listes obtiennent la
list.copy
méthode:En Python 3.5:
Faire un autre pointeur ne fait pas de copie
my_list
est juste un nom qui pointe vers la liste réelle en mémoire. Lorsque vous ditesnew_list = my_list
que 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.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
deepcopy
dans lecopy
module :Pour montrer comment cela nous permet de créer de nouvelles sous-listes:
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:
En Python 2.7 64 bits:
sur Python 3.5 64 bits:
la source
list_copy=[]
for item in list: list_copy.append(copy(item))
et c'est beaucoup plus rapide.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_list
et 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 vousmy_list
y référez comme persisteront lorsque vous vous y référer en tant quenew_list
. Chacune des autres réponses à cette question vous donne différentes façons de créer un nouvel objet auquel vous liernew_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.
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.
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.
Consultez la documentation pour plus d'informations sur les cas d'angle lors de la copie.
la source
Utilisation
thing[:]
la source
Commençons par le début et explorons cette question.
Supposons donc que vous ayez deux listes:
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
copy
sur notre liste d'originelist_1
:Maintenant, si vous pensez copier copié la liste_1, alors vous vous trompez. La
id
fonction peut nous montrer si deux variables peuvent pointer vers le même objet. Essayons ça:La sortie est:
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 un
list
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: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:
production:
Il a donc modifié la liste d'origine:
Passons maintenant à une méthode pythonique pour copier des listes.
Cette méthode corrige le premier problème rencontré:
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:
Essayons maintenant de modifier la liste et voyons si nous sommes toujours confrontés au problème précédent:
La sortie est:
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.
list_2
doit faire référence à un autre objet qui est une copie delist_2
. Allons vérifier:Nous obtenons la sortie:
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:
Cela nous donne la sortie:
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:
Vous copiez uniquement la liste externe, pas la liste interne. Nous pouvons à nouveau utiliser la
id
fonction pour vérifier cela.La sortie est:
Lorsque nous le faisons
copy_2=list_2[:]
, cela se produit: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
deepcopy
fonction.Vérifions ceci:
Les deux listes externes ont des ID différents, essayons ceci sur les listes imbriquées internes.
La sortie est:
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: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:
Il génère:
Comme vous pouvez le voir, il n'a pas modifié la liste imbriquée d'origine, il a uniquement modifié la liste copiée.
la source
L'idiome de Python pour ce faire est
newList = oldList[:]
la source
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, = list
etnew_list = [*list]
):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 ):
la source
b=[*a]
- la seule façon évidente de le faire;).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 leslist
objets 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 rapidedeepcopy
.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 leslist
objets imbriqués dans leold_list
et lenew_list
, et les modifications apportées à l'un deslist
objets sont perpétuées dans l'autre.Edit: De nouvelles informations mises à jour
Comme d'autres l'ont indiqué, il existe des problèmes de performances importants en utilisant le
copy
module etcopy.deepcopy
pour les listes multidimensionnelles .la source
repr()
est suffisante pour recréer l'objet. C'est aussieval()
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.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 deinput()
,sys.agrv
ou 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.new_list = eval(repr(old_list))
, donc en plus d'être une mauvaise idée, il est probablement beaucoup trop lent pour fonctionner.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.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.
la source
old_list
et ilnew_list
y 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).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):
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:
. Bonus : Si vous ne voulez pas copier les éléments, utilisez (aka copie superficielle):
Comprenons la différence entre la solution # 1 et la solution # 2
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.
la source
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()
oucopy.deepcopy()
plutôt que les alternatives, par exemple en Python 3:Les sorties:
la source
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)
la source
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)
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:
Il en utilise un nouveau pour les listes:
Et deepcopy_list ressemble à ceci:
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.
la source
Une légère perspective pratique pour regarder dans la mémoire à travers id et gc.
la source
N'oubliez pas qu'en Python lorsque vous faites:
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.la source
L'option de copie profonde est la seule méthode qui fonctionne pour moi:
conduit à la sortie de:
la source
En effet, la ligne
new_list = my_list
attribue une nouvelle référence à la variablemy_list
quinew_list
est similaire auC
code donné ci-dessous,Vous devez utiliser le module de copie pour créer une nouvelle liste en
la source