Comment zip (* [iter (s)] * n) fonctionne-t-il en Python?

103
s = [1,2,3,4,5,6,7,8,9]
n = 3

zip(*[iter(s)]*n) # returns [(1,2,3),(4,5,6),(7,8,9)]

Comment ça zip(*[iter(s)]*n)marche? À quoi ressemblerait-il s'il était écrit avec un code plus détaillé?

Oliver Zheng
la source
1
jetez également un œil ici où son fonctionnement est également expliqué: stackoverflow.com/questions/2202461/…
Matt Joiner
si les réponses ici ne suffisent pas, je l'ai bloguée
telliott99
7
Bien que très intrigante, cette technique doit aller à l'encontre de la valeur fondamentale de «lisibilité» de Python!
Demis

Réponses:

108

iter()est un itérateur sur une séquence. [x] * nproduit une liste contenant la nquantité de x, c'est-à-dire une liste de longueur n, où se trouve chaque élément x. *argdécompresse une séquence en arguments pour un appel de fonction. Par conséquent, vous passez le même itérateur 3 fois à zip(), et il extrait un élément de l'itérateur à chaque fois.

x = iter([1,2,3,4,5,6,7,8,9])
print zip(x, x, x)
Ignacio Vazquez-Abrams
la source
1
Bon à savoir: lorsqu'un itérateur yields (= returns) un élément, vous pouvez imaginer cet élément comme "consommé". Ainsi, la prochaine fois que l'itérateur sera appelé, il produira le prochain élément "non consommé".
winklerrr
46

Les autres bonnes réponses et commentaires expliquent bien les rôles du déballage des arguments et de zip () .

Comme le disent Ignacio et ujukatzel , vous passez à zip()trois références au même itérateur et faites zip()3-tuples des entiers - dans l'ordre - de chaque référence à l'itérateur:

1,2,3,4,5,6,7,8,9  1,2,3,4,5,6,7,8,9  1,2,3,4,5,6,7,8,9
^                    ^                    ^            
      ^                    ^                    ^
            ^                    ^                    ^

Et puisque vous demandez un exemple de code plus détaillé:

chunk_size = 3
L = [1,2,3,4,5,6,7,8,9]

# iterate over L in steps of 3
for start in range(0,len(L),chunk_size): # xrange() in 2.x; range() in 3.x
    end = start + chunk_size
    print L[start:end] # three-item chunks

Suivre les valeurs de startet end:

[0:3) #[1,2,3]
[3:6) #[4,5,6]
[6:9) #[7,8,9]

FWIW, vous pouvez obtenir le même résultat map()avec un argument initial de None:

>>> map(None,*[iter(s)]*3)
[(1, 2, 3), (4, 5, 6), (7, 8, 9)]

Pour plus d'informations sur zip()et map(): http://muffinresearch.co.uk/archives/2007/10/16/python-transposing-lists-with-map-and-zip/

viande_mécanique
la source
31

Je pense qu'une chose qui a manqué dans toutes les réponses (probablement évidente pour ceux qui connaissent les itérateurs) mais pas si évidente pour les autres est -

Puisque nous avons le même itérateur, il est consommé et les éléments restants sont utilisés par le zip. Donc, si nous avons simplement utilisé la liste et non l'iter par exemple.

l = range(9)
zip(*([l]*3)) # note: not an iter here, the lists are not emptied as we iterate 
# output 
[(0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), (6, 6, 6), (7, 7, 7), (8, 8, 8)]

En utilisant l'itérateur, affiche les valeurs et ne garde que la disponibilité, donc pour zip, une fois que 0 est consommé, 1 est disponible, puis 2 et ainsi de suite. Une chose très subtile, mais assez intelligente !!!

gabhijit
la source
+1, tu m'as sauvé! Je ne peux pas croire que d'autres réponses aient sauté ce détail vital en supposant que tout le monde le sache. Pouvez-vous donner des références à une documentation contenant ces informations?
Snehasish Karmakar
9

iter(s) renvoie un itérateur pour s.

[iter(s)]*n fait une liste de n fois le même itérateur pour s.

Ainsi, en faisant zip(*[iter(s)]*n), il extrait un élément des trois itérateurs de la liste dans l'ordre. Puisque tous les itérateurs sont le même objet, il regroupe simplement la liste en morceaux de n.

sttwister
la source
7
Pas «n itérateurs de la même liste», mais «n fois le même objet itérateur». Différents objets itérateur ne partagent pas l'état, même lorsqu'ils appartiennent à la même liste.
Thomas Wouters
Merci, corrigé. En effet, c'était ce à quoi je "pensais", mais j'ai écrit autre chose.
sttwister
6

Un conseil pour utiliser zip de cette façon. Cela tronquera votre liste si sa longueur n'est pas également divisible. Pour contourner ce problème, vous pouvez utiliser itertools.izip_longest si vous pouvez accepter les valeurs de remplissage. Ou vous pouvez utiliser quelque chose comme ceci:

def n_split(iterable, n):
    num_extra = len(iterable) % n
    zipped = zip(*[iter(iterable)] * n)
    return zipped if not num_extra else zipped + [iterable[-num_extra:], ]

Usage:

for ints in n_split(range(1,12), 3):
    print ', '.join([str(i) for i in ints])

Impressions:

1, 2, 3
4, 5, 6
7, 8, 9
10, 11
jmagnusson
la source
3
Ceci est déjà documenté dans les itertoolsrecettes: docs.python.org/2/library/itertools.html#recipes grouper . Pas besoin de réinventer la roue
jamylak
1

Il est probablement plus facile de voir ce qui se passe dans l'interpréteur python ou ipythonavec n = 2:

In [35]: [iter("ABCDEFGH")]*2
Out[35]: [<iterator at 0x6be4128>, <iterator at 0x6be4128>]

Donc, nous avons une liste de deux itérateurs qui pointent vers le même objet itérateur. Rappelez-vous que itersur un objet renvoie un objet itérateur et dans ce scénario, c'est le même itérateur deux fois en raison du *2sucre syntaxique python. Les itérateurs ne fonctionnent également qu'une seule fois.

De plus, zipprend n'importe quel nombre d'itérables (les séquences sont itérables ) et crée un tuple à partir du i'ème élément de chacune des séquences d'entrée. Puisque les deux itérateurs sont identiques dans notre cas, zip déplace le même itérateur deux fois pour chaque tuple à 2 éléments de sortie.

In [41]: help(zip)
Help on built-in function zip in module __builtin__:

zip(...)
    zip(seq1 [, seq2 [...]]) -> [(seq1[0], seq2[0] ...), (...)]

    Return a list of tuples, where each tuple contains the i-th element
    from each of the argument sequences.  The returned list is truncated
    in length to the length of the shortest argument sequence.

L' opérateur unpacking ( *) garantit que les itérateurs s'exécutent jusqu'à l'épuisement, ce qui dans ce cas est jusqu'à ce qu'il n'y ait pas assez d'entrée pour créer un tuple à 2 éléments.

Cela peut être étendu à n'importe quelle valeur de net zip(*[iter(s)]*n)fonctionne comme décrit.

Akhan
la source
Désolé d'être lent. Mais pourriez-vous expliquer le "le même itérateur deux fois en raison du sucre syntaxique python * 2. Les itérateurs ne s'exécutent également qu'une seule fois." partie s'il vous plaît? Si oui, comment se fait-il que le résultat ne soit pas [("A", "A") ....]? Merci.
Bowen Liu
@BowenLiu *est simplement pratique pour dupliquer un objet. Essayez-le avec des scalaires puis avec des listes. Essayez aussi print(*zip(*[iter("ABCDEFG")]*2))vs print(*zip(*[iter("ABCDEFG"), iter("ABCDEFG")])). Ensuite, commencez à découper les deux en étapes plus petites pour voir quels sont réellement les objets itérateur dans les deux instructions.
akhan le