Que sont les fonctions Python Itertools ?



Selon la documentation de Python, le module itertools offre un ensemble d’outils efficaces en termes de rapidité et d’utilisation de la mémoire pour manipuler les itérateurs en Python. Ces instruments, utilisables individuellement ou en combinaison, permettent de créer et d’interagir avec des itérateurs de manière concise et performante.

Ce module propose des fonctions facilitant l’opération sur les itérateurs, notamment lors du traitement de grands volumes de données. Les fonctions d’itertools peuvent être appliquées aux itérateurs existants pour générer des itérateurs Python encore plus sophistiqués.

De plus, itertools contribue à réduire les erreurs lors de la manipulation d’itérateurs et à produire un code plus propre, lisible et maintenable.

Selon leurs fonctionnalités, les itérateurs du module itertools peuvent être classés en plusieurs catégories :

#1. Itérateurs Infini

Ces itérateurs permettent de manipuler des séquences illimitées et d’exécuter des boucles sans fin, à moins d’une condition d’arrêt. Ils sont particulièrement utiles pour simuler des boucles infinies ou pour générer des séquences sans limite. itertools propose trois itérateurs infinis : count(), cycle() et repeat().

#2. Itérateurs Combinatoires

Les itérateurs combinatoires regroupent des fonctions destinées à travailler sur les produits cartésiens et à effectuer des combinaisons et des permutations d’éléments d’un itérable. Ils sont indispensables pour explorer toutes les manières possibles d’organiser ou de combiner des éléments. itertools comprend quatre itérateurs combinatoires : product(), permutations(), combinations() et combinations_with_replacement().

#3. Itérateurs Terminant sur la Séquence d’Entrée la Plus Courte

Ces itérateurs, utilisés sur des séquences finies, produisent une sortie déterminée par la fonction utilisée. Parmi eux, on trouve : accumulate(), chain(), chain.from_iterable(), compress(), dropwhile(), filterfalse(), groupby(), islice(), pairwise(), starmap(), takewhile(), tee() et zip_longest().

Examinons le fonctionnement de ces différentes fonctions d’itertools en fonction de leur catégorie :

Itérateurs Infini

Les trois itérateurs infinis sont :

#1. count()

La fonction count(start, step) génère une séquence infinie de nombres en partant d’une valeur initiale. Elle accepte deux arguments optionnels : start, qui définit la valeur de départ de la séquence, et step, qui détermine la différence entre deux nombres consécutifs. La valeur de départ par défaut est 0, et le pas par défaut est 1.

import itertools
# Compte à partir de 4, avec des pas de 2
for i in itertools.count(4, 2):
    # Condition pour arrêter la boucle et éviter l'itération infinie
    if i == 14:
        break
    else:
        print(i) # Affichage : 4, 6, 8, 10, 12

Résultat:

4
6
8
10
12

#2. cycle()

La fonction cycle(iterable) reçoit un itérable en paramètre et parcourt cet itérable en permettant l’accès aux éléments dans l’ordre où ils apparaissent.

Par exemple, si on passe ["rouge", "vert", "jaune"] à cycle(), au premier cycle on aura accès à « rouge », au second à « vert », puis à « jaune ». Au quatrième cycle, comme tous les éléments ont été parcourus, on recommencera avec « rouge » et ainsi de suite sans fin.

Lorsque vous utilisez cycle(), il est recommandé de stocker le résultat dans une variable pour créer un itérateur qui conserve son état. Cela garantit que le cycle ne redémarre pas à chaque itération, et vous permet d’accéder à l’élément suivant à chaque appel.

import itertools

couleurs = ["rouge", "vert", "jaune"]
# Passez les couleurs à cycle()
cycle_couleurs = itertools.cycle(couleurs)
print(cycle_couleurs)

# Utilisation de range pour arrêter la boucle après 7 impressions
# next() est utilisé pour obtenir l'élément suivant de l'itérateur
for i in range(7):
    print(next(cycle_couleurs))

Résultat:

rouge
vert
jaune
rouge
vert
jaune
rouge

#3. repeat()

repeat(elem, n) accepte deux paramètres : l’élément à répéter (elem) et le nombre de répétitions (n). L’élément peut être une valeur unique ou un itérable. Si n n’est pas spécifié, l’élément sera répété indéfiniment.

import itertools
   
for i in itertools.repeat(10, 3):
    print(i)

Résultat:

10 
10
10

Itérateurs Combinatoires

Les itérateurs combinatoires comprennent:

#1. product()

product() calcule le produit cartésien de l’itérable qui lui est donné. Si on a deux ensembles, par exemple x = {7,8} et y = {1,2,3}, le produit cartésien de x et y contiendra toutes les combinaisons possibles d’éléments de x et y, avec le premier élément venant de x et le second de y. Le produit cartésien de x et y sera alors [(7, 1), (7, 2), (7, 3), (8, 1), (8, 2), (8, 3)].

product() prend un paramètre optionnel nommé repeat qui permet de calculer le produit cartésien d’un itérable avec lui-même. repeat spécifie le nombre de répétitions pour chaque élément des itérables d’entrée lors du calcul du produit cartésien.

Par exemple, product('ABCD', repeat=2) donne des combinaisons telles que (‘A’, ‘A’), (‘A’, ‘B’), (‘A’, ‘C’), etc. Si repeat est égal à 3, la fonction produira des combinaisons comme (‘A’, ‘A’, ‘A’), (‘A’, ‘A’, ‘B’), (‘A’, ‘A’, ‘C’), (‘A’, ‘A’, ‘D’), et ainsi de suite.

from itertools import product
# product() avec l'argument optionnel repeat
print("product() avec l'argument optionnel repeat ")
print(list(product('ABC', repeat = 2)))

# product sans repeat
print("product() SANS argument optionnel repeat")
print(list(product([7,8], [1,2,3])))

Résultat

product() avec l'argument optionnel repeat 
[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'), ('B', 'C'), ('C', 'A'), ('C', 'B'), ('C', 'C')]
product() SANS argument optionnel repeat
[(7, 1), (7, 2), (7, 3), (8, 1), (8, 2), (8, 3)]

#2. permutations()

permutations(iterable, group_size) renvoie toutes les permutations possibles de l’itérable passé en argument. Une permutation représente le nombre de manières dont les éléments d’un ensemble peuvent être ordonnés. permutations() prend un argument optionnel group_size. Si group_size n’est pas spécifié, les permutations générées auront la même taille que la longueur de l’itérable passé à la fonction.

import itertools
nombres = [1, 2, 3]
permutations_taillees = list(itertools.permutations(nombres,2))
permutations_non_taillees = list(itertools.permutations(nombres))

print("Permutations avec une taille de 2")
print(permutations_taillees)
print("Permutations SANS argument de taille")
print(permutations_non_taillees)

Résultat

Permutations avec une taille de 2
[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
Permutations SANS argument de taille
[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]

#3. combinations()

combinations(iterable, size) retourne toutes les combinaisons possibles d’un itérable d’une longueur donnée à partir des éléments de l’itérable passé à la fonction. L’argument size spécifie la taille de chaque combinaison.

Les résultats sont ordonnés. La combinaison diffère légèrement des permutations. Avec la permutation, l’ordre compte, mais avec la combinaison, l’ordre n’a pas d’importance. Par exemple, dans [A, B, C], il y a 6 permutations : AB, AC, BA, BC, CA, CB, mais seulement 3 combinaisons : AB, AC, BC.

import itertools
nombres = [1, 2, 3,4]
combinaison_taille2 = list(itertools.combinations(nombres,2))
combinaison_taille3 = list(itertools.combinations(nombres, 3))

print("Combinaisons avec une taille de 2")
print(combinaison_taille2)
print("Combinaisons avec une taille de 3")
print(combinaison_taille3)

Résultat:

Combinaisons avec une taille de 2
[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
Combinaisons avec une taille de 3
[(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]

#4. combinations_with_replacement()

combinations_with_replacement(iterable, size) génère toutes les combinaisons possibles d’un itérable d’une longueur donnée à partir de l’itérable passé à la fonction, tout en autorisant les éléments répétés dans les combinaisons de sortie. La taille détermine la taille des combinaisons générées.

Cette fonction diffère de combinations() car elle donne des combinaisons où un élément peut être répété plusieurs fois. Par exemple, vous pouvez obtenir une combinaison comme (1,1), ce qui n’est pas possible avec combination().

import itertools
nombres = [1, 2, 3,4]

combinaison_taille2 = list(itertools.combinations_with_replacement(nombres,2))
print("Combinations_with_replacement => taille 2")
print(combinaison_taille2)

Résultat

Combinations_with_replacement => taille 2
[(1, 1), (1, 2), (1, 3), (1, 4), (2, 2), (2, 3), (2, 4), (3, 3), (3, 4), (4, 4)]

Itérateurs Terminant

Cela comprend des itérateurs tels que :

#1. accumulate()

accumulate(iterable, fonction) prend un itérable et un second argument optionnel qui est une fonction. Il renvoie ensuite le résultat cumulé de l’application de la fonction à chaque itération sur les éléments de l’itérable. Si aucune fonction n’est transmise, l’addition est effectuée et les résultats cumulés sont renvoyés.

import itertools
import operator
nombres = [1, 2, 3, 4, 5]

# Accumuler la somme des nombres
valeur_accumulee = itertools.accumulate(nombres)
accumulation_multiplication = itertools.accumulate(nombres, operator.mul)
print("Accumuler sans fonction")
print(list(valeur_accumulee))
print("Accumuler avec multiplication")
print(list(accumulation_multiplication))

Résultat:

Accumuler sans fonction
[1, 3, 6, 10, 15]
Accumuler avec multiplication
[1, 2, 6, 24, 120]

#2. chain()

chain(iterable_1, iterable_2, ...) prend plusieurs itérables et les chaîne en produisant un seul itérable contenant les valeurs des itérables passés à la fonction chain().

import itertools

lettres = ['A', 'B', 'C', 'D']
nombres = [1, 2, 3]
couleurs = ['rouge', 'vert', 'jaune']

# Chainer lettres et nombres
iterable_chaine = list(itertools.chain(lettres, nombres, couleurs))
print(iterable_chaine)

Résultat:

['A', 'B', 'C', 'D', 1, 2, 3, 'rouge', 'vert', 'jaune']

#3. chain.from_iterable()

chain.from_iterable(iterable) Cette fonction est semblable à chain(). Cependant, elle diffère car elle ne prend qu’un seul itérable contenant des sous-itérables et les enchaîne.

import itertools

lettres = ['A', 'B', 'C', 'D']
nombres = [1, 2, 3]
couleurs = ['rouge', 'vert', 'jaune']

iterable = ['bonjour',couleurs, lettres, nombres]
chaine = list(itertools.chain.from_iterable(iterable))
print(chaine)

Résultat:

['b', 'o', 'n', 'j', 'o', 'u', 'r', 'rouge', 'vert', 'jaune', 'A', 'B', 'C', 'D', 1, 2, 3]

#4. compress()

compress(data, selectors) prend deux arguments : data qui est un itérable, et selectors qui est un itérable contenant des valeurs booléennes true et false. Les valeurs 1 et 0 peuvent également être utilisées comme alternatives aux valeurs booléennes true et false. compress() filtre ensuite les données passées en utilisant les éléments correspondants transmis dans le sélecteur.

Les valeurs dans data qui correspondent à la valeur true ou 1 dans le sélecteur sont sélectionnées, tandis que celles qui correspondent à false ou 0 sont ignorées. Si vous passez moins de booléens dans les sélecteurs que le nombre d’éléments dans les données, tous les éléments au-delà des booléens passés dans les sélecteurs sont ignorés.

import itertools

# data a 10 elements
data = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
# passage de 9 éléments selecteur
selectors = [True, False, 1, False, 0, 1, True, False, 1]

# Selection des éléments dans les données selon selectors
données_filtrées = list(itertools.compress(data, selectors))
print(données_filtrées)

Résultat:

['A', 'C', 'F', 'G', 'I']

#5. dropwhile()

dropwhile(fonction, séquence) prend une fonction avec une condition qui renvoie vrai ou faux, et une séquence de valeurs. Il supprime ensuite toutes les valeurs jusqu’à ce que la condition transmise renvoie False. Une fois que la condition renvoie false, les autres éléments sont inclus dans ses résultats, qu’ils renvoient True ou False.

import itertools

nombres = [1, 2, 3, 4, 5, 1, 6, 7, 2, 1, 8, 9, 0, 7]

# Supprime les éléments jusqu'à ce que la condition passée soit False
nombres_filtrés = list(itertools.dropwhile(lambda x: x < 5, nombres))
print(nombres_filtrés)

Résultat:

[5, 1, 6, 7, 2, 1, 8, 9, 0, 7]

#6. filterfalse()

filterfalse(fonction, séquence) prend une fonction avec une condition qui évalue vrai ou faux, et une séquence. Il renvoie ensuite les valeurs de la séquence qui ne satisfont pas la condition de la fonction.

import itertools

nombres = [1, 2, 3, 4, 2, 3 ,5, 6, 5, 8, 1, 2, 3, 6, 2, 7, 4, 3]

# Filtrer les éléments pour lesquels la condition est False
nombres_filtrés = list(itertools.filterfalse(lambda x: x < 4, nombres))
print(nombres_filtrés)

Résultat:

[4, 5, 6, 5, 8, 6, 7, 4]

#7. groupby()

groupby(iterable, key) prend un itérable et une clé, puis crée un itérateur qui renvoie des clés et des groupes consécutifs. Pour que cela fonctionne, l’itérable qui lui est transmis doit être trié sur la même fonction clé. La fonction clé calcule une valeur clé pour chaque élément de l’itérable.

import itertools

liste_entree = [("Domestique", "Vache"), ("Domestique", "Chien"), ("Domestique", "Chat"),("Sauvage", "Lion"), ("Sauvage", "Zèbre"), ("Sauvage", "Éléphant")]
classification = itertools.groupby(liste_entree,lambda x: x[0])
for cle,valeur in classification:
  print(cle,":",list(valeur))

Résultat:

Domestique : [('Domestique', 'Vache'), ('Domestique', 'Chien'), ('Domestique', 'Chat')]
Sauvage : [('Sauvage', 'Lion'), ('Sauvage', 'Zèbre'), ('Sauvage', 'Éléphant')]

#8. islice()

islice(iterable, start, stop, step) vous permet de découper un itérable en utilisant les valeurs start, stop et step passées. L’argument step est facultatif. Le comptage commence à partir de 0 et l’élément au numéro d’arrêt n’est pas inclus.

import itertools

nombres = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

# Sélectionne les éléments dans un intervalle
nombres_selectionnés = list(itertools.islice(nombres, 2, 10))
nombres_selectionnés_pas = list(itertools.islice(nombres, 2, 10,2))
print("islice sans définir de valeur de pas")
print(nombres_selectionnés)
print("islice avec une valeur de pas de 2")
print(nombres_selectionnés_pas)

Résultat:

islice sans définir de valeur de pas
[3, 4, 5, 6, 7, 8, 9, 10]
islice avec une valeur de pas de 2
[3, 5, 7, 9]

#9. pairwise()

pairwise(iterable) renvoie des paires superposées successives extraites de l’itérable passé dans l’ordre dans lequel elles apparaissent dans l’itérable. Si l’itérable passé a moins de deux valeurs, le résultat de pairwise() sera vide.

from itertools import pairwise

nombres = [1, 2, 3, 4, 5, 6, 7, 8]
mot = 'MONDE'
unique = ['A']

print(list(pairwise(nombres)))
print(list(pairwise(mot)))
print(list(pairwise(unique)))

Résultat:

[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8)]
[('M', 'O'), ('O', 'N'), ('N', 'D'), ('D', 'E')]
[]

#10. starmap()

starmap(fonction, iterable) est une fonction utilisée à la place de map() lorsque les paramètres d’argument sont déjà regroupés en tuples. startmap() applique une fonction aux éléments de l’itérable passé. L’itérable doit avoir des éléments regroupés en tuples.

import itertools

iter_starmap = [(123, 63, 13), (5, 6, 52), (824, 51, 9), (26, 24, 16), (14, 15, 11)]
print (list(itertools.starmap(min, iter_starmap)))

Résultat:

[13, 5, 9, 16, 11]

#11. takewhile()

takewhile(function, iterable) fonctionne à l’opposé de dropwhile(). takewhile() prend une fonction avec une condition à évaluer et un itérable. Il inclut ensuite tous les éléments de l’itérable qui satisfont la condition dans la fonction jusqu’à ce que False soit renvoyé. Une fois que False est renvoyé, tous les éléments suivants de l’itérable sont ignorés.

import itertools

nombres = [1, 2, 3, 4, 5, 1, 6, 7, 2, 1, 8, 9, 0, 7]

# Sélectionne les éléments jusqu'à ce que la condition passée soit False
nombres_filtrés = list(itertools.takewhile(lambda x: x < 5, nombres))
print(nombres_filtrés)

Résultat:

[1, 2, 3, 4]

#12. tee()

tee(iterable, n) prend un itérable et renvoie plusieurs itérateurs indépendants. Le nombre d’itérateurs à renvoyer est défini par n, qui par défaut est 2.

import itertools

nombres = [1, 2, 3, 4, 5]

# Créer deux itérateurs indépendants à partir des nombres
iter1, iter2 = itertools.tee(nombres, 2)
print(list(iter1))
print(list(iter2))

Résultat:

[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]

#13. zip_longest()

zip_longest(iterables, fillvalue) accepte plusieurs itérateurs et une valeur de remplissage. Il retourne ensuite un itérateur qui regroupe les éléments de chacun des itérateurs qui lui sont passés. Si les itérateurs ne sont pas de la même longueur, les valeurs manquantes sont remplacées par la fillvalue passée à la fonction jusqu’à épuisement de l’itérable le plus long.

import itertools

noms = ['Jean', 'Mathieu', 'Marie', 'Alice', 'Bob', 'Charlie', 'Fury']
ages = [25, 30, 12, 13, 42]

# Combiner les noms et âges, en remplissant les âges manquants avec un tiret
combiné = itertools.zip_longest(noms, ages, fillvalue="-")

for nom, age in combined:
    print(nom, age)

Résultat:

Jean 25
Mathieu 30
Marie 12
Alice 13
Bob 42
Charlie -
Fury -

Conclusion

Les itertools de Python sont un ensemble d’outils essentiel pour tout développeur Python. Ils sont largement utilisés dans la programmation fonctionnelle, le traitement et la transformation de données, le filtrage et la sélection de données, le regroupement et l’agrégation, la combinaison d’itérables, la combinatoire et la manipulation de séquences infinies.

En tant que développeur Python, vous tirerez un grand avantage de l’apprentissage des itertools. Utilisez cet article pour vous familiariser avec ce module puissant.