Utilisation de la fonction zip () de Python pour l’itération parallèle

Utilisation de la fonction zip () de Python pour l'itération parallèle

La fonctionzip() de Python crée un itérateur qui agrégera les éléments de deux ou plusieurs itérables. Vous pouvez utiliser l'itérateur résultant pour résoudre rapidement et systématiquement des problèmes de programmation courants, comme la création dedictionaries. Dans ce didacticiel, vous découvrirez la logique de la fonction Pythonzip() et comment vous pouvez l'utiliser pour résoudre des problèmes réels.

À la fin de ce didacticiel, vous apprendrez:

  • Commentzip() fonctionne à la fois dans Python 3 et Python 2

  • Comment utiliser la fonction Pythonzip() pourparallel iteration

  • Commentcreate dictionaries à la volée en utilisantzip()

Free Bonus:5 Thoughts On Python Mastery, un cours gratuit pour les développeurs Python qui vous montre la feuille de route et l'état d'esprit dont vous aurez besoin pour faire passer vos compétences Python au niveau supérieur.

Comprendre la fonction Pythonzip()

zip() est disponible dans lesbuilt-in namespace. Si vous utilisezdir() pour inspecter__builtins__, vous verrezzip() à la fin de la liste:

>>>

>>> dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', ..., 'zip']

Vous pouvez voir que'zip' est la dernière entrée dans la liste des objets disponibles.

Selon lesofficial documentation, la fonctionzip() de Python se comporte comme suit:

Renvoie un itérateur de tuples, où lei-ème tuple contient l'élémenti-ème de chacune des séquences d'arguments ou des itérables. L'itérateur s'arrête lorsque l'entrée la plus courte itérable est épuisée. Avec un seul argument itérable, il renvoie un itérateur de 1-tuples. Sans argument, il renvoie un itérateur vide. (Source)

Vous décompacterez cette définition dans le reste du didacticiel. En parcourant les exemples de code, vous verrez que les opérations de fermeture éclair Python fonctionnent exactement comme la fermeture à glissière physique d'un sac ou d'une paire de jeans. Des paires de dents imbriquées des deux côtés de la fermeture éclair sont tirées ensemble pour fermer une ouverture. En fait, cette analogie visuelle est parfaite pour comprendrezip(), puisque la fonction a été nommée d'après les fermetures à glissière physiques!

Utilisation dezip() en Python

La fonctionzip() de Python est définie commezip(*iterables). La fonction prenditerables comme arguments et renvoie uniterator. Cet itérateur génère une série de tuples contenant des éléments de chaque itérable. zip() peut accepter tout type d'itérable, tel quefiles,lists, tuples,dictionaries,sets, etc.

Passer des argumentsn

Si vous utilisezzip() avec des argumentsn, la fonction retournera un itérateur qui génère des tuples de longueurn. Pour voir cela en action, jetez un œil au bloc de code suivant:

>>>

>>> numbers = [1, 2, 3]
>>> letters = ['a', 'b', 'c']
>>> zipped = zip(numbers, letters)
>>> zipped  # Holds an iterator object

>>> type(zipped)

>>> list(zipped)
[(1, 'a'), (2, 'b'), (3, 'c')]

Ici, vous utilisezzip(numbers, letters) pour créer un itérateur qui produit des tuples de la forme(x, y). Dans ce cas, les valeursx sont tirées denumbers et les valeursy sont tirées deletters. Remarquez comment la fonction Pythonzip() renvoie un itérateur. Pour récupérer l'objet de liste final, vous devez utiliserlist() pour consommer l'itérateur.

Si vous travaillez avec des séquences telles que des listes, des tuples ou desstrings, vos itérables sont assurés d'être évalués de gauche à droite. Cela signifie que la liste résultante de tuples prendra la forme[(numbers[0], letters[0]), (numbers[1], letters[1]),..., (numbers[n], letters[n])]. Cependant, pour d'autres types d'itérables (commesets), vous pourriez voir des résultats étranges:

>>>

>>> s1 = {2, 3, 1}
>>> s2 = {'b', 'a', 'c'}
>>> list(zip(s1, s2))
[(1, 'a'), (2, 'c'), (3, 'b')]

Dans cet exemple,s1 ets2 sont des objetsset, qui ne conservent pas leurs éléments dans un ordre particulier. Cela signifie que les tuples retournés parzip() auront des éléments qui sont appariés au hasard. Si vous comptez utiliser la fonction Pythonzip() avec des itérables non ordonnés comme des ensembles, alors gardez ceci à l'esprit.

Pas d'arguments

Vous pouvez également appelerzip() sans argument. Dans ce cas, vous obtiendrez simplement un itérateur vide:

>>>

>>> zipped = zip()
>>> zipped

>>> list(zipped)
[]

Ici, vous appelezzip() sans argument, donc votre variablezipped contient un itérateur vide. Si vous utilisez l'itérateur aveclist(), vous verrez également une liste vide.

Vous pouvez également essayer de forcer l'itérateur vide à produire directement un élément. Dans ce cas, vous obtiendrez unStopIterationexception:

>>>

>>> zipped = zip()
>>> next(zipped)
Traceback (most recent call last):
  File "", line 1, in 
StopIteration

Lorsque vous appeleznext() surzipped, Python essaie de récupérer l'élément suivant. Cependant, puisquezipped contient un itérateur vide, il n'y a rien à extraire, donc Python lève une exceptionStopIteration.

Passer un argument

La fonctionzip() de Python ne peut également prendre qu’un seul argument. Le résultat sera un itérateur qui produira une série de tuples à 1 élément:

>>>

>>> a = [1, 2, 3]
>>> zipped = zip(a)
>>> list(zipped)
[(1,), (2,), (3,)]

Ce n'est peut-être pas très utile, mais cela fonctionne toujours. Vous pouvez peut-être trouver des cas d'utilisation de ce comportement dezip()!

Comme vous pouvez le voir, vous pouvez appeler la fonction Pythonzip() avec autant d'itérables d'entrée que vous en avez besoin. La longueur des tuples résultants sera toujours égale au nombre d'itérables que vous passez comme arguments. Voici un exemple avec trois itérables:

>>>

>>> integers = [1, 2, 3]
>>> letters = ['a', 'b', 'c']
>>> floats = [4.0, 5.0, 6.0]
>>> zipped = zip(integers, letters, floats)  # Three input iterables
>>> list(zipped)
[(1, 'a', 4.0), (2, 'b', 5.0), (3, 'c', 6.0)]

Ici, vous appelez la fonction Pythonzip() avec trois itérables, de sorte que les tuples résultants ont chacun trois éléments.

Passer des arguments de longueur inégale

Lorsque vous travaillez avec la fonction Pythonzip(), il est important de faire attention à la longueur de vos itérables. Il est possible que les itérables que vous transmettez en tant qu'arguments ne soient pas de la même longueur.

Dans ces cas, le nombre d'éléments quezip() émet sera égal à la longueur de l'itérableshortest. Les éléments restants dans les itérables plus longs seront totalement ignorés parzip(), comme vous pouvez le voir ici:

>>>

>>> list(zip(range(5), range(100)))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

Puisque5 est la longueur du premier (et le plus court) objetrange(),zip() renvoie une liste de cinq tuples. Il y a toujours 95 éléments sans correspondance du deuxième objetrange(). Ils sont tous ignorés parzip() car il n'y a plus d'éléments du premier objetrange() pour compléter les paires.

Si les valeurs de fin ou sans correspondance sont importantes pour vous, vous pouvez utiliseritertools.zip_longest() au lieu dezip(). Avec cette fonction, les valeurs manquantes seront remplacées par ce que vous passerez à l'argumentfillvalue (par défaut àNone). L'itération se poursuivra jusqu'à épuisement de la plus longue itérable:

>>>

>>> from itertools import zip_longest
>>> numbers = [1, 2, 3]
>>> letters = ['a', 'b', 'c']
>>> longest = range(5)
>>> zipped = zip_longest(numbers, letters, longest, fillvalue='?')
>>> list(zipped)
[(1, 'a', 0), (2, 'b', 1), (3, 'c', 2), ('?', '?', 3), ('?', '?', 4)]

Ici, vous utilisezitertools.zip_longest() pour générer cinq tuples avec des éléments deletters,numbers etlongest. L'itération ne s'arrête que lorsquelongest est épuisé. Les éléments manquants denumbers etletters sont remplis d'un point d'interrogation?, ce que vous avez spécifié avecfillvalue.

Comparaison dezip() en Python 3 et 2

La fonctionzip() de Python fonctionne différemment dans les deux versions du langage. Dans Python 2,zip() renvoie unlist de tuples. Lelist résultant est tronqué à la longueur de l'entrée la plus courte itérable. Si vous appelezzip() sans argument, vous obtenez unlist vide en retour:

>>>

>>> # Python 2
>>> zipped = zip(range(3), 'ABCD')
>>> zipped  # Hold a list object
[(0, 'A'), (1, 'B'), (2, 'C')]
>>> type(zipped)

>>> zipped = zip()  # Create an empty list
>>> zipped
[]

Dans ce cas, votre appel à la fonction Pythonzip() renvoie une liste de tuples tronqués à la valeurC. Lorsque vous appelezzip() sans argument, vous obtenez unlist vide.

Dans Python 3, cependant,zip() renvoie uniterator. Cet objet produit des tuples à la demande et ne peut être parcouru qu'une seule fois. L'itération se termine par une exceptionStopIteration une fois que l'itérable d'entrée le plus court est épuisé. Si vous ne fournissez aucun argument àzip(), la fonction renvoie un itérateur vide:

>>>

>>> # Python 3
>>> zipped = zip(range(3), 'ABCD')
>>> zipped  # Hold an iterator

>>> type(zipped)

>>> list(zipped)
[(0, 'A'), (1, 'B'), (2, 'C')]
>>> zipped = zip()  # Create an empty iterator
>>> zipped

>>> next(zipped)
Traceback (most recent call last):
  File "", line 1, in 
    next(zipped)
StopIteration

Ici, votre appel àzip() renvoie un itérateur. La première itération est tronquée àC, et la seconde entraîne une exceptionStopIteration. Dans Python 3, vous pouvez également émuler le comportement Python 2 dezip() en enveloppant l'itérateur renvoyé dans un appel àlist(). Cela passera par l'itérateur et retournera une liste de tuples.

Si vous utilisez régulièrement Python 2, notez que l'utilisation dezip() avec des itérables d'entrée longs peut involontairement consommer beaucoup de mémoire. Dans ces situations, envisagez d'utiliseritertools.izip(*iterables) à la place. Cette fonction crée un itérateur qui agrège les éléments de chacun des itérables. Il produit le même effet quezip() en Python 3:

>>>

>>> # Python 2
>>> from itertools import izip
>>> zipped = izip(range(3), 'ABCD')
>>> zipped

>>> list(zipped)
[(0, 'A'), (1, 'B'), (2, 'C')]

Dans cet exemple, vous appelezitertools.izip() pour créer un itérateur. Lorsque vous utilisez l'itérateur renvoyé aveclist(), vous obtenez une liste de tuples, comme si vous utilisiezzip() dans Python 3. L'itération s'arrête lorsque la plus courte entrée itérable est épuisée.

Si vous avez vraiment besoin d'écrire du code qui se comporte de la même manière dans Python 2 et Python 3, vous pouvez utiliser une astuce comme celle-ci:

try:
    from itertools import izip as zip
except ImportError:
    pass

Ici, siizip() est disponible dansitertools, alors vous saurez que vous êtes en Python 2 etizip() sera importé en utilisant l'aliaszip. Sinon, votre programme lèvera unImportError et vous saurez que vous êtes en Python 3. (Lepass statement ici n'est qu'un espace réservé.)

Avec cette astuce, vous pouvez utiliser en toute sécurité la fonction Pythonzip() dans tout votre code. Une fois exécuté, votre programme sélectionnera et utilisera automatiquement la bonne version.

Jusqu'à présent, vous avez décrit le fonctionnement de la fonctionzip() de Python et découvert certaines de ses fonctionnalités les plus importantes. Il est maintenant temps de retrousser vos manches et de commencer à coder des exemples concrets!

Boucle sur plusieurs itérables

La boucle sur plusieurs itérables est l’un des cas d’utilisation les plus courants de la fonctionzip() de Python. Si vous devez parcourir plusieurs listes, tuples ou toute autre séquence, il est probable que vous utilisiezzip(). Cette section vous montrera comment utiliserzip() pour parcourir plusieurs itérables en même temps.

Traverser des listes en parallèle

La fonctionzip() de Python vous permet d’effectuer une itération en parallèle sur au moins deux itérables. Puisquezip() génère des tuples, vous pouvez les décompresser dans l'en-tête d'unfor loop:

>>>

>>> letters = ['a', 'b', 'c']
>>> numbers = [0, 1, 2]
>>> for l, n in zip(letters, numbers):
...     print(f'Letter: {l}')
...     print(f'Number: {n}')
...
Letter: a
Number: 0
Letter: b
Number: 1
Letter: c
Number: 2

Ici, vous parcourez la série de tuples renvoyés parzip() et décompressez les éléments enl etn. Lorsque vous combinez les boucleszip(),for ettuple unpacking, vous pouvez obtenir un idiome utile etPythonic pour parcourir deux ou plusieurs itérables à la fois.

Vous pouvez également parcourir plus de deux itérables dans une seule bouclefor. Prenons l'exemple suivant, qui comporte trois itérables d'entrée:

>>>

>>> letters = ['a', 'b', 'c']
>>> numbers = [0, 1, 2]
>>> operators = ['*', '/', '+']
>>> for l, n, o in zip(letters, numbers, operators):
...     print(f'Letter: {l}')
...     print(f'Number: {n}')
...     print(f'Operator: {o}')
...
Letter: a
Number: 0
Operator: *
Letter: b
Number: 1
Operator: /
Letter: c
Number: 2
Operator: +

Dans cet exemple, vous utilisezzip() avec trois itérables pour créer et renvoyer un itérateur qui génère des tuples à 3 éléments. Cela vous permet de parcourir les trois itérables en une seule fois. Il n'y a aucune restriction sur le nombre d'itérables que vous pouvez utiliser avec la fonctionzip() de Python.

Note: Si vous souhaitez approfondir les bouclesfor de Python, consultezPython “for” Loops (Definite Iteration).

Traversée des dictionnaires en parallèle

Dans Python 3.6 et au-delà, les dictionnaires sontordered collections, ce qui signifie qu'ils conservent leurs éléments dans le même ordre dans lequel ils ont été introduits. Si vous profitez de cette fonctionnalité, vous pouvez utiliser la fonction Pythonzip() pour parcourir plusieurs dictionnaires de manière sûre et cohérente:

>>>

>>> dict_one = {'name': 'John', 'last_name': 'Doe', 'job': 'Python Consultant'}
>>> dict_two = {'name': 'Jane', 'last_name': 'Doe', 'job': 'Community Manager'}
>>> for (k1, v1), (k2, v2) in zip(dict_one.items(), dict_two.items()):
...     print(k1, '->', v1)
...     print(k2, '->', v2)
...
name -> John
name -> Jane
last_name -> Doe
last_name -> Doe
job -> Python Consultant
job -> Community Manager

Ici, vous parcourezdict_one etdict_two en parallèle. Dans ce cas,zip() génère des tuples avec les éléments des deux dictionnaires. Ensuite, vous pouvez décompresser chaque tuple et accéder simultanément aux éléments des deux dictionnaires.

Note: Si vous souhaitez approfondir l'itération du dictionnaire, consultezHow to Iterate Through a Dictionary in Python.

Notez que, dans l'exemple ci-dessus, l'ordre d'évaluation de gauche à droite est garanti. Vous pouvez également utiliser la fonctionzip() de Python pour parcourir des ensembles en parallèle. Cependant, vous devez tenir compte du fait que, contrairement aux dictionnaires de Python 3.6, les ensemblesdon’t gardent leurs éléments dans l'ordre. Si vous oubliez ce détail, le résultat final de votre programme peut ne pas être exactement ce que vous voulez ou ce que vous attendez.

Décompresser une séquence

Il y a une question qui revient fréquemment dans les forums pour les nouveaux pythonistes: "S'il y a une fonctionzip(), alors pourquoi n'y a-t-il pas de fonctionunzip() qui fasse le contraire?"

La raison pour laquelle il n'y a pas de fonctionunzip() en Python est que l'opposé dezip() est… enfin,zip(). Vous rappelez-vous que la fonction Pythonzip() fonctionne comme une vraie fermeture éclair? Jusqu'à présent, les exemples vous ont montré comment Python ferme les fermetures à glissière. Alors, comment décompressez-vous les objets Python?

Supposons que vous ayez une liste de tuples et que vous souhaitiez séparer les éléments de chaque tuple en séquences indépendantes. Pour ce faire, vous pouvez utiliserzip() avec lesunpacking operator *, comme ceci:

>>>

>>> pairs = [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
>>> numbers, letters = zip(*pairs)
>>> numbers
(1, 2, 3, 4)
>>> letters
('a', 'b', 'c', 'd')

Ici, vous avez unlist de tuples contenant une sorte de données mixtes. Ensuite, vous utilisez l'opérateur de décompression* pour décompresser les données, en créant deux listes différentes (numbers etletters).

Tri en parallèle

Sorting est une opération courante en programmation. Supposons que vous souhaitiez combiner deux listes et les trier en même temps. Pour ce faire, vous pouvez utiliserzip() avec.sort() comme suit:

>>>

>>> letters = ['b', 'a', 'd', 'c']
>>> numbers = [2, 4, 3, 1]
>>> data1 = list(zip(letters, numbers))
>>> data1
[('b', 2), ('a', 4), ('d', 3), ('c', 1)]
>>> data1.sort()  # Sort by letters
>>> data1
[('a', 4), ('b', 2), ('c', 1), ('d', 3)]
>>> data2 = list(zip(numbers, letters))
>>> data2
[(2, 'b'), (4, 'a'), (3, 'd'), (1, 'c')]
>>> data2.sort()  # Sort by numbers
>>> data2
[(1, 'c'), (2, 'b'), (3, 'd'), (4, 'a')]

Dans cet exemple, vous combinez d'abord deux listes aveczip() et les triez. Remarquez commentdata1 est trié parletters etdata2 est trié parnumbers.

Vous pouvez également utilisersorted() etzip() ensemble pour obtenir un résultat similaire:

>>>

>>> letters = ['b', 'a', 'd', 'c']
>>> numbers = [2, 4, 3, 1]
>>> data = sorted(zip(letters, numbers))  # Sort by letters
>>> data
[('a', 4), ('b', 2), ('c', 1), ('d', 3)]

Dans ce cas,sorted() parcourt l'itérateur généré parzip() et trie les éléments parletters, le tout en une seule fois. Cette approche peut être un peu plus rapide car vous n’aurez besoin que de deux appels de fonction:zip() etsorted().

Avecsorted(), vous écrivez également un morceau de code plus général. Cela vous permettra de trier n'importe quel type de séquence, pas seulement les listes.

Calcul en paires

Vous pouvez utiliser la fonction Pythonzip() pour effectuer des calculs rapides. Supposons que vous ayez les données suivantes dans une feuille de calcul:

Element/Month janvier février Mars

Ventes totales

52 000,00

51 000,00

48 000,00

Coût de production

46 800,00

45 900,00

43 200,00

Vous allez utiliser ces données pour calculer votre profit mensuel. zip() peut vous fournir un moyen rapide d'effectuer les calculs:

>>>

>>> total_sales = [52000.00, 51000.00, 48000.00]
>>> prod_cost = [46800.00, 45900.00, 43200.00]
>>> for sales, costs in zip(total_sales, prod_cost):
...     profit = sales - costs
...     print(f'Total profit: {profit}')
...
Total profit: 5200.0
Total profit: 5100.0
Total profit: 4800.0

Ici, vous calculez le profit pour chaque mois en soustrayantcosts desales. La fonctionzip() de Python combine les bonnes paires de données pour effectuer les calculs. Vous pouvez généraliser cette logique pour effectuer tout type de calcul complexe avec les paires renvoyées parzip().

Création de dictionnaires

Lesdictionaries de Python sont une structure de données très utile. Parfois, vous devrez peut-être créer un dictionnaire à partir de deux séquences différentes mais étroitement liées. Un moyen pratique pour y parvenir est d'utiliser ensembledict() etzip(). Par exemple, supposons que vous ayez récupéré les données d'une personne à partir d'un formulaire ou d'une base de données. Vous disposez maintenant des listes de données suivantes:

>>>

>>> fields = ['name', 'last_name', 'age', 'job']
>>> values = ['John', 'Doe', '45', 'Python Developer']

Avec ces données, vous devez créer un dictionnaire pour un traitement ultérieur. Dans ce cas, vous pouvez utiliserdict() aveczip() comme suit:

>>>

>>> a_dict = dict(zip(fields, values))
>>> a_dict
{'name': 'John', 'last_name': 'Doe', 'age': '45', 'job': 'Python Developer'}

Ici, vous créez un dictionnaire qui combine les deux listes. zip(fields, values) renvoie un itérateur qui génère des tuples à 2 éléments. Si vous appelezdict() sur cet itérateur, vous construirez le dictionnaire dont vous avez besoin. Les éléments defields deviennent les clés du dictionnaire et les éléments devalues représentent les valeurs du dictionnaire.

Vous pouvez également mettre à jour un dictionnaire existant en combinantzip() avecdict.update(). Supposons que John change son travail et que vous ayez besoin de mettre à jour le dictionnaire. Vous pouvez faire quelque chose comme ceci:

>>>

>>> new_job = ['Python Consultant']
>>> field = ['job']
>>> a_dict.update(zip(field, new_job))
>>> a_dict
{'name': 'John', 'last_name': 'Doe', 'age': '45', 'job': 'Python Consultant'}

Ici,dict.update() met à jour le dictionnaire avec le tuple clé-valeur que vous avez créé à l'aide de la fonctionzip() de Python. Avec cette technique, vous pouvez facilement écraser la valeur dejob.

Conclusion

Dans ce didacticiel, vous avez appris à utiliser la fonctionzip() de Python. zip() peut recevoir plusieurs itérables en entrée. Il renvoie un itérateur qui peut générer des tuples avec des éléments appariés à partir de chaque argument. L'itérateur résultant peut être très utile lorsque vous devez traiter plusieurs itérables en une seule boucle et effectuer certaines actions sur leurs éléments en même temps.

Maintenant vous pouvez:

  • Use the zip() function dans Python 3 et Python 2

  • Loop over multiple iterables et effectuer différentes actions sur leurs éléments en parallèle

  • Create and update dictionaries à la volée en compressant deux itérables d'entrée ensemble

Vous avez également codé quelques exemples que vous pouvez utiliser comme point de départ pour implémenter vos propres solutions en utilisant la fonctionzip() de Python. N'hésitez pas à modifier ces exemples au fur et à mesure que vous explorezzip() en profondeur!