Je connais product()
depuis bel lurette, et je n’avais jamais réalisé son utilité. Des fois on a le truc sous les yeux, comme ça, et on voit rien.
Vous savez, on veut parfois parcourir tous les trucs, et leur appliquer tous les machins. Ca donne une boucle imbriquée :
res = [] for truc in trucs: for machin in machins: res.append(bidule(machin, truc)) |
Un code parfaitement légitime, clair, lisible. Mais l’envie de faire une liste en intension est si forte !
C’est là que product()
intervient, avec ses petits bras musclés :
from itertools import product res = [ bidule(machin, truc) for truc, machin in product(trucs, machins)] |
Python va magiquement créer autant de tuples (machin, truc)
qu’il y en a besoin.
Et parce que ça fait un mois que j’ai pas mis d’article, faut prendre tout de suite les bonnes habitudes :
Petite coquille je suppose :
res = [ bidule(machin, truc) for truc, machin in product(trucs, machins)]
product(truc, machin) -> product(trucS, machinS)
et puis starmap :
res = starmap(bidule, product(truc, machin) )
On peut arguer que c’est moins lisible mais ça reste un iterateur lazy (et évite donc la construction de la liste).
J’ai découvert il y a peu de temps les compréhensions multiples.
On peut faire la même chose avec :
[bidule(machin, truc) for truc in trucs for machin in machins]
@o: la list comprehension devient vite moins lisible, particulièrement avec des noms longs et si il y a plus de deux itérables. Par ailleurs, elle ne fonctionne que si on souhaite faire des opérations simples car on n’a pas la possibilité de faire un bloc de plusieurs lignes pour le résutlat.
et butt c’est avec deux T :D
Si je comprends bien ça évite juste de faire
res = [[ bidule(machin, truc) for truc in trucs] for machin in machins]
oui?
Un petit comment pour ajouter qu’au niveau temps d’exécution l’utilisation de product semble meilleure.
Pour machins et trucs qui valent range(1000), avec le double for on a un temps d’exécution de 0.25 s contre 0.12 s pour le product.
C’est super pratique lorsqu’on veut faire une boucle imbriquée sur un nombre d’éléments variable, comme les array numpy.
Exemple pour faire (x**2).sum():
sum(x[e]**2 for e in itertools.product(*map(range, x.shape)))
Belle photo, vous êtes à gauche non ?
C’est comme zip(), ou j’ai pas compris ?
@furankun:
Dans la docstring de product:
Donc, oui c’est la même chose, sauf que ça te renvoie un itérateur au lieu d’une liste.
@floweb: Non, zip ne fait qu’une seule association là où product te fait toutes les associations possible:
In [11]: a = [1, 2, 3]
In [12]: b = ['a', 'b', 'c']
In [13]: for i in itertools.product(a, b):
print(i)
....:
(1, 'a')
(1, 'b')
(1, 'c')
(2, 'a')
(2, 'b')
(2, 'c')
(3, 'a')
(3, 'b')
(3, 'c')
Ok, merci @Ryzz ! TIL.
@furankun:
Oups, ce n’est pas exactement la même chose:
In [18]: [[(i, j) for j in b] for i in a]
Out[18]:
[[(1, 'a'), (1, 'b'), (1, 'c')],
[(2, 'a'), (2, 'b'), (2, 'c')],
[(3, 'a'), (3, 'b'), (3, 'c')]]
In [19]: [(i, j) for j in b for i in a]
Out[19]:
[(1, 'a'),
(2, 'a'),
(3, 'a'),
(1, 'b'),
(2, 'b'),
(3, 'b'),
(1, 'c'),
(2, 'c'),
(3, 'c')]
Plutôt sympa comme astuce, je penserai à l’utiliser!
Par contre je trouve qu’on ne voit pas clairement dans quel ordre va se dérouler l’itération (quel “for” sera imbriqué), donc dans le but de rester explicite c’est peut-être mieux de ne l’utiliser seulement lorsque l’ordre n’est pas important ?
@Symen : l’imbrication se fera selon, l’ordre des éléments passés à “product()”. Typiquement, dans l’exemple, il commencera par itérer “trucs” et pour chaque élément de “trucs” il y aura une itération sur “machins”…