Python >> Tutoriel Python >  >> Python

Comment copier une liste en Python :trancher, copier, etc.

Rebonjour! Bienvenue dans le sixième volet de la série How to Python. Aujourd'hui, nous allons apprendre à cloner ou copier une liste en Python. Contrairement à la plupart des articles de cette série, il existe en fait plusieurs options, certaines meilleures que d'autres.

En bref, il existe de nombreuses façons de copier une liste. Dans cet article seul, nous partageons huit solutions. Si vous cherchez quelque chose de sûr, utilisez la méthode de copie (c'est-à-dire my_list.copy() ). Sinon, n'hésitez pas à essayer de trancher (c'est-à-dire my_list[:] ) ou le constructeur de liste (c'est-à-dire list(my_list) ).

Résumé de la vidéo

https://youtu.be/ZMCte_LHml0

Si vous n'êtes pas intéressé à parcourir cet article, j'ai partagé tout le matériel dans une vidéo YouTube. En plus de coder en direct la plupart des solutions au problème de copie de liste, j'ai également partagé certaines mesures de performance ainsi que ma solution au défi ci-dessous. Si pour rien d'autre, j'aimerais que vous alliez sur YouTube et augmentiez un peu mes statistiques (comme, commentez, abonnez-vous, etc.).

Présentation du problème

Imaginons que nous ayons une liste :

my_list = [27, 13, -11, 60, 39, 15]

Et, nous voulons créer un double de cette liste, afin que nous puissions modifier leur contenu indépendamment :

my_list = [27, 13, -11, 60, 39, 15]
my_duplicate_list = [27, 13, -11, 60, 39, 15]

my_list.append(17)
print(my_list)  # prints [27, 13, -11, 60, 39, 15, 17]
print(my_duplicate_list)  # prints [27, 13, -11, 60, 39, 15]

Comment ferions-nous cela? Eh bien, avant de plonger, il y a quelques sujets que nous devrions probablement aborder en premier. Après tout, le clonage peut être un peu contre-intuitif, il est donc important que nous prenions du recul pour discuter des références en double et des copies complètes.

Références en double

Si vous êtes venu à cet article, c'est probablement parce que vous avez essayé de cloner une liste à la main et que vous avez rencontré des problèmes. Par exemple :

my_list = [27, 13, -11, 60, 39, 15]
my_duplicate_list = my_list  # done

Malheureusement, cela ne fait pas vraiment le travail. Après tout, nous n'avons pas réellement dupliqué la liste. Nous avons simplement stocké la référence à celle-ci dans une autre variable. Si nous essayons de modifier notre liste de doublons, nous modifierons également l'original. Jetez un œil :

my_duplicate_list.append(7)
print(my_duplicate_list)  # prints [27, 13, -11, 60, 39, 15, 7]
print(my_list)  # prints [27, 13, -11, 60, 39, 15, 7]

Donc, clairement, ce n'est pas ce que nous voulons. Au lieu de dupliquer notre liste, nous avons créé un alias —une autre variable qui fait référence à la même liste.

Copies profondes

De plus, nous devrions probablement couvrir ce que l'on appelle la copie en profondeur. Disons que nous avons une liste qui contient des listes :

my_list = [[27], [13], [-11], [60], [39], [15]]

Si nous décidons d'effectuer une simple copie sur cette liste, nous nous retrouverons avec un comportement étrange :

my_list_copy = copy(my_list)  # a placeholder copy function
print(my_list_copy)  # prints [[27], [13], [-11], [60], [39], [15]]  # prints as expected

D'accord, donc pas encore de problèmes. En fait, nous pouvons même ajouter des informations à la nouvelle liste sans aucun problème :

my_list_copy.append([17])
print(my_list_copy)  # prints [[27], [13], [-11], [60], [39], [15], [17]]
print(my_list)  # prints [[27], [13], [-11], [60], [39], [15]]

Cependant, si nous décidons de modifier l'une des listes imbriquées, nous rencontrerons des problèmes :

my_list_copy[0].append(12)
print(my_list_copy)  # prints [[27, 12], [13], [-11], [60], [39], [15], [17]]
print(my_list)  # prints [[27, 12], [13], [-11], [60], [39], [15]]

C'est parce que notre opération de copie n'a dupliqué que la liste externe. En d'autres termes, nous avons créé deux listes distinctes, mais chaque liste stocke les mêmes références exactes . Modifier une référence dans une liste la modifie dans l'autre liste.

Une méthode de copie en profondeur s'assurerait de copier à la fois la liste externe et la liste interne. Gardez cela à l'esprit à mesure que nous avançons.

Solutions

Si nous voulons cloner une liste, nous avons plusieurs options. Jetons un coup d'œil.

Copier une liste par Brute Force

Comme toujours, Python propose plusieurs solutions rapides à ce problème. Cependant, avant d'en arriver là, je veux en fait examiner le clonage du point de vue d'un débutant. En d'autres termes, laissons de côté l'API pour l'instant et essayons d'implémenter notre propre fonction de clonage :

def clone(my_list):
    my_list_clone = list()
    for item in my_list:
        my_list_clone.append(item)
    return my_list_clone

Cela semble assez simple. Fondamentalement, nous parcourons simplement la liste et copions chaque élément dans la nouvelle liste. En fait, nous pouvons même rendre cette solution plus pythonique :

def clone(my_list):
    return [item for item in my_list]

Comment est-ce pour un one-liner? Le problème est que cette méthode n'effectue pas de clonage en profondeur. Malheureusement, l'implémentation manuelle du clonage profond est un peu hors de portée de ce didacticiel, mais je vous mets au défi de l'essayer vous-même. À titre indicatif, vous voudrez essentiellement créer une fonction de copie récursive.

Copier une liste à l'aide d'une tranche

Si vous pensiez que la compréhension était fluide, attendez de voir cette tranche :

my_list = [27, 13, -11, 60, 39, 15]
my_duplicate_list = my_list[:]  # done

Si vous n'êtes pas familier avec les tranches, cela prend essentiellement un "sous-ensemble" de la liste de bout en bout. Normalement, nous utiliserions des tranches comme celle-ci :

my_list[:4]  # [27, 13, -11, 60]
my_list[3:]  # [60, 39, 15]

Sans index, la tranche dupliquera la liste entière. Encore une fois, cependant, cela n'effectuera pas de copie complète.

Copier une liste à l'aide du constructeur de liste

Dans le monde des modèles de conception de logiciels, il existe un modèle de création connu sous le nom de constructeur de copie. Au lieu de prendre un ensemble de paramètres d'entrée pour la construction, un constructeur de copie prend une référence à un objet initialisé et en produit une copie. Heureusement pour nous, Python fournit un constructeur de copie pour les listes :

my_list = [27, 13, -11, 60, 39, 15]
my_duplicate_list = list(my_list)

Malheureusement, même cette méthode ne fournit pas de copie en profondeur, mais elle est beaucoup plus lisible que la tranche.

Copier une liste à l'aide d'une expression étoilée

Récemment, l'utilisateur de dev.to, Leah Einhorn, m'a indiqué une autre façon de copier une liste en Python :

my_list = [27, 13, -11, 60, 39, 15]
my_duplicate_list = [*my_list]

Faute d'un meilleur terme, je vais aller de l'avant et appeler cette solution une "expression étoilée" car c'est l'erreur de syntaxe que j'ai eue quand j'ai tout gâché :

SyntaxError: can't use starred expression here

Cela dit, je pense que le terme technique pour cette solution serait iterable unpacking dont j'ai parlé dans les articles suivants :

  • Comment obtenir le dernier élément d'une liste en Python
  • Les fonctionnalités les plus cool du langage de programmation Python

Dans tous les cas, cela fonctionne en développant la liste en arguments séparés par des virgules. En d'autres termes, au lieu de stocker la liste dans une autre liste, nous décompressons la liste en éléments individuels et les chargeons directement dans une nouvelle liste.

Comme c'est le cas avec la plupart des solutions de cette liste, le déballage itérable est également la proie des mêmes problèmes de copie superficielle. Par conséquent, vous ne pourrez pas utiliser cette solution pour copier une liste de listes (voir le défi ci-dessous).

Copier une liste à l'aide de la fonction de copie

Il s'avère que Python 3.3+ inclut directement une fonction de copie pour les listes. Pour l'utiliser, appelez le copy() sur la liste et stocker les résultats :

my_list = [1, -5, 13, 4, 7]
my_duplicate_list = my_list.copy()

À mon avis, c'est l'option la plus lisible, et je pense que c'est aussi la version la plus efficace. En fait, les performances devraient être similaires à la tranche. Cependant, Python change beaucoup, donc les autres méthodes mentionnées ici peuvent être tout aussi performantes. Consultez mes benchmarks ci-dessous pour plus d'informations.

Copier une liste à l'aide du package de copie

Python ne serait pas Python sans sa collection de packages sans fin. En conséquence, vous pouvez probablement imaginer qu'il existe une API que nous pouvons utiliser pour effectuer la copie pour nous. Après tout, pourquoi devrions-nous réinventer la roue ? Voici comment cela fonctionne :

import copy
my_list = [27, 13, -11, 60, 39, 15]
my_duplicate_list = copy.copy(my_list)

En raison de la nature générique de cette méthode, nous prenons un peu de mal en termes de performances. Cela dit, ce que nous essayons d'accomplir ici est assez clair. Malheureusement, nous ne parvenons toujours pas à produire une copie complète. Heureusement, le package de copie a une solution pour cela :

my_list = [[27], [13], [-11], [60], [39], [15]]
my_duplicate_list = copy.deepcopy(my_list)

Enfin, nous avons réalisé une véritable copie profonde de nos listes imbriquées. Bien sûr, la copie en profondeur est tout à fait exagérée si la liste n'a qu'une seule couche de profondeur. Consultez le défi ci-dessous si vous êtes intéressé par d'autres moyens de réaliser une copie complète.

Copier une liste en utilisant la multiplication

Honnêtement, j'ai hésité à mettre celui-ci ici parce que c'est tout simplement ridicule, mais c'est un abus amusant de l'opérateur de multiplication :

my_list = [27, 13, -11, 60, 39, 15]
my_list_copy = my_list * 1

Encore une fois, cela n'effectue pas une copie en profondeur, mais ce n'est pas le but. Nous avons juste utilisé l'opérateur de multiplication pour dupliquer une liste. Normalement, nous utiliserions l'opérateur de multiplication pour remplir une liste :

my_list = [0] * 100  # a list with 100 zeroes

Au lieu de cela, nous avons décidé d'en abuser dans le but de générer une copie de la liste. Si vous pensez que c'est drôle, jetez un œil à cette liste de fonctionnalités de langage étranges sur Stack Overflow. Après avoir écrit cette section, je suis tombé sur cet article en essayant de trouver d'autres moyens d'abuser des fonctionnalités du langage Python.

Performances

Si vous n'avez pas encore regardé le résumé vidéo, ce serait le moment idéal pour consulter la section des performances. Après tout, je récupère toutes les mesures à partir de là.

En tout cas, pour vérifier les performances, j'aime utiliser le timeit bibliothèque qui nous permet de vérifier la vitesse d'un extrait de code. Et si nous exécutons tous nos extraits de code, nous obtiendrons une belle comparaison relative. Pour commencer, nous devons construire notre ensemble de chaînes :

setup = """
pens = ["Sidney Crosby", "Evgeni Malkin", "Kris Letang"]
import copy
"""

brute = """
my_list_clone = list()
for item in pens:
    my_list_clone.append(item)
"""

comprehension = """
my_duplicate_list = [item for item in pens]
"""

sliced = """
my_duplicate_list = pens[:]
"""

constructor = """
my_duplicate_list = list(pens)
"""

starred = """
my_duplicate_list = [*pens]
"""

copied = """
my_duplicate_list = pens.copy()
"""

copy_pack = """
my_duplicate_list = copy.copy(pens)
"""

multiplication = """
my_duplicate_list = pens * 1
"""

Avec ces chaînes en place, il suffit de les exécuter en utilisant le timeit bibliothèque :

>>> import timeit
>>> min(timeit.repeat(setup=setup, stmt=brute, repeat=10))
0.36501209999994444
>>> min(timeit.repeat(setup=setup, stmt=comprehension, repeat=10))
0.24934929999994893
>>> min(timeit.repeat(setup=setup, stmt=sliced, repeat=10))
0.07904620000022078
>>> min(timeit.repeat(setup=setup, stmt=constructor, repeat=10))
0.15885279999997692
>>> min(timeit.repeat(setup=setup, stmt=starred, repeat=10))
0.056014600000025894
>>> min(timeit.repeat(setup=setup, stmt=copied, repeat=10))
0.081436100000019
>>> min(timeit.repeat(setup=setup, stmt=copy_pack, repeat=10))
0.37341589999982716
>>> min(timeit.repeat(setup=setup, stmt=multiplication, repeat=10))
0.07483669999987796

Et voilà, nous l'avons ! Les huit solutions entièrement testées. Naturellement, j'étais intrigué par les solutions les plus rapides, alors j'ai décidé de voir comment elles évoluaient. Voici la chaîne de configuration mise à jour qui génère une liste de 1 000 éléments :

setup = """
pens = ["Sidney Crosby" for _ in range(1000)]
import copy
"""

Voici les résultats des tests mis à jour avec les 4 meilleures solutions :

>>> min(timeit.repeat(setup=setup, stmt=sliced, repeat=10))
3.097306200000048
>>> min(timeit.repeat(setup=setup, stmt=starred, repeat=10))
2.9019645000000764
>>> min(timeit.repeat(setup=setup, stmt=copied, repeat=10))
3.033651100000043
>>> min(timeit.repeat(setup=setup, stmt=multiplication, repeat=10))
2.897438200000124

Dans l'ensemble, il semble que les quatre solutions évoluent à peu près au même rythme. En d'autres termes, il n'y a pas beaucoup de différence au-delà des frais généraux initiaux. Peut-être qu'ils s'aggravent avec plus d'articles, mais je n'ai pas vraiment la patience de tester davantage. Peut-être que quelqu'un peut jeter un coup d'œil pour nous !

Quoi qu'il en soit, si vous êtes intéressé par les solutions rapides, consultez les solutions de tranche, d'expression étoilée, de copie et de multiplication. Bien sûr, je dirais que la fonction de copie intégrée est la solution, quelle que soit la vitesse.

Défi

Maintenant que nous avons couvert plusieurs mécanismes de copie en Python, j'ai pensé qu'il serait amusant de proposer un petit défi. En particulier, écrivez du code pour dupliquer une liste imbriquée. Par exemple :

pens_forwards = [
  ["Dominik Simon", "Sidney Crosby", "Jake Guentzel"],
  ["Alex Galchenyuk", "Evgeni Malkin", "Patric Hornqvist"],
  ["Zach Aston-Reese", "Nick Bjugstad", "Bryan Rust"],
  ["Brandon Tanev", "Teddy Blueger", "Dominik Kahun"]
]

pens_forwards_copy = duplicate(pens_forwards)  # implement this

Ensuite, vous devriez être en mesure de confirmer que votre copie a fonctionné en testant l'identité des sous-listes :

pens_forwards is pens_forwards_copy  # Should return false
pens_forwards[0] is pens_forwards_copy[0]  # Should return false

Lorsque vous êtes prêt, partagez votre solution dans les commentaires. Cela devrait fonctionner pour tout type de liste imbriquée, mais nous supposerons une profondeur de 1 (c'est-à-dire une liste à 2 dimensions). De plus, nous supposerons que les éléments de la liste la plus profonde sont des primitives ou au moins immuables (c'est-à-dire des nombres, des chaînes, etc.). Enfin, vous ne pouvez pas utiliser le deepcopy fonction. Au lieu de cela, vous devriez implémenter le vôtre.

Voici ma solution !

Si vous souhaitez vous essayer à ce défi, partagez votre solution sur Twitter avec le hashtag #RenegadePython. Si je le vois, je lui donnerai une part !

Un petit récapitulatif

Avec cet épisode de How to Python, nous commençons enfin à aborder des fonctionnalités et des sujets de langage plus intéressants. En conséquence, nous trouvons de nombreuses façons de résoudre le même problème - certaines bonnes, d'autres mauvaises. Quoi qu'il en soit, voici toutes les façons de cloner une liste en Python :

my_list = [27, 13, -11, 60, 39, 15]

# Clone a list by brute force
my_duplicate_list = [item for item in my_list]

# Clone a list with a slice
my_duplicate_list = my_list[:]

# Clone a list with the list constructor
my_duplicate_list = list(my_list) 

# Clone a list with a starred expression
my_duplicate_list = [*my_list]

# Clone a list with the copy function (Python 3.3+)
my_duplicate_list = my_list.copy()  # preferred method

# Clone a list with the copy package
import copy
my_duplicate_list = copy.copy(my_list)
my_deep_duplicate_list = copy.deepcopy(my_list)

# Clone a list with multiplication?
my_duplicate_list = my_list * 1  # do not do this

Toutes ces méthodes feront le travail, mais une seule de ces méthodes effectuera réellement une copie complète si nécessaire. Quoi qu'il en soit, nous en avons terminé ici.

Si vous avez trouvé cet article utile, pensez à le partager sur les réseaux sociaux ou à laisser un commentaire ci-dessous. Jusqu'à la prochaine fois !