itertools.product fait sauter des boucles imbriquées


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 :

Hommes nus assis sur un homme nus

for but in buts

13 thoughts on “itertools.product fait sauter des boucles imbriquées

  • Poisson

    Petite coquille je suppose :

    res = [ bidule(machin, truc) for truc, machin in product(trucs, machins)]

  • LB

    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).

  • o

    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]

    • Sam Post author

      @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.

  • furankun

    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?

  • trnsnt

    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.

  • Matthieu

    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 ?

  • floweb

    C’est comme zip(), ou j’ai pas compris ?

    a = [1, 2, 3]

    b = ['a', 'b', 'c']

    for i, j in zip(a, b):

    ... print i, j

    ...

    1 a

    2 b

    3 c

  • Ryzz

    @furankun:

    Dans la docstring de product:

    Cartesian product of input iterables. Equivalent to nested for-loops.

    For example, product(A, B) returns the same as: ((x,y) for x in A for y in B).

    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')

  • Ryzz

    @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')]

  • Symen

    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 ?

  • Fred

    @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”…

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.