¿Cómo funciona zip (* [iter (s)] * n) 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)]

¿Cómo zip(*[iter(s)]*n)funciona? ¿Cómo se vería si estuviera escrito con un código más detallado?

Oliver Zheng
fuente
1
También eche un vistazo aquí donde también se explica cómo funciona: stackoverflow.com/questions/2202461/…
Matt Joiner
si las respuestas aquí no son suficientes, lo escribí en un blog aquí: telliott99.blogspot.com/2010/01/…
telliott99
7
Aunque es muy intrigante, esta técnica debe ir en contra del valor central de "legibilidad" de Python.
Demis

Respuestas:

108

iter()es un iterador sobre una secuencia. [x] * nproduce una lista que contiene la ncantidad de x, es decir, una lista de longitud n, donde está cada elemento x. *argdescomprime una secuencia en argumentos para una llamada a función. Por lo tanto, está pasando el mismo iterador 3 veces a zip(), y cada vez extrae un elemento del iterador.

x = iter([1,2,3,4,5,6,7,8,9])
print zip(x, x, x)
Ignacio Vázquez-Abrams
fuente
1
Es bueno saberlo: cuando un iterador yields (= returns) de un elemento, se puede imaginar este artículo como "consumido". Por tanto, la próxima vez que se llame al iterador, se obtendrá el siguiente elemento "no consumido".
winklerrr
46

Las otras excelentes respuestas y comentarios explican bien los roles de descomprimir argumentos y zip () .

Como dicen Ignacio y ujukatzel , pasas a zip()tres referencias al mismo iterador y haces zip()3 tuplas de los enteros, en orden, de cada referencia al iterador:

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
^                    ^                    ^            
      ^                    ^                    ^
            ^                    ^                    ^

Y dado que solicita una muestra de código más detallada:

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

Siguiendo los valores de starty end:

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

FWIW, puede obtener el mismo resultado map()con un argumento inicial de None:

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

Para obtener más información sobre zip()y map(): http://muffinresearch.co.uk/archives/2007/10/16/python-transposing-lists-with-map-and-zip/

carne_mecánica
fuente
31

Creo que una cosa que se perdió en todas las respuestas (probablemente obvia para aquellos familiarizados con los iteradores) pero no tan obvia para otros es:

Como tenemos el mismo iterador, se consume y el zip utiliza los elementos restantes. Entonces, si simplemente usamos la lista y no el iter, por ejemplo.

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

Usando el iterador, muestra los valores y solo permanece disponible, por lo que para zip, una vez que se consume 0, 1 está disponible y luego 2 y así sucesivamente. ¡Una cosa muy sutil, pero bastante inteligente!

gabhijit
fuente
+1, ¡me salvaste! No puedo creer que otras respuestas omitieran este detalle vital asumiendo que todos lo saben. ¿Puede dar alguna referencia a una documentación que incluya esta información?
Snehasish Karmakar
9

iter(s) devuelve un iterador para s.

[iter(s)]*n hace una lista de n veces el mismo iterador para s.

Entonces, al hacerlo zip(*[iter(s)]*n), extrae un elemento de los tres iteradores de la lista en orden. Dado que todos los iteradores son el mismo objeto, solo agrupa la lista en partes de n.

sttwister
fuente
7
No 'n iteradores de la misma lista', sino 'n veces el mismo objeto iterador'. Los diferentes objetos de iterador no comparten el estado, incluso cuando pertenecen a la misma lista.
Thomas Wouters
Gracias, corregido. De hecho, eso era lo que estaba "pensando", pero escribí algo más.
sttwister
6

Un consejo para usar zip de esta manera. Truncará su lista si su longitud no es divisible de manera uniforme. Para solucionar esto, puede usar itertools.izip_longest si puede aceptar valores de relleno. O podrías usar algo como esto:

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:], ]

Uso:

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

Huellas dactilares:

1, 2, 3
4, 5, 6
7, 8, 9
10, 11
jmagnusson
fuente
3
Esto ya está documentado en itertoolsrecetas: docs.python.org/2/library/itertools.html#recipes grouper . No es necesario reinventar la rueda
jamylak
1

Probablemente sea más fácil ver lo que está sucediendo en el intérprete de Python o ipythoncon n = 2:

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

Entonces, tenemos una lista de dos iteradores que apuntan al mismo objeto iterador. Recuerde que iteren un objeto devuelve un objeto iterador y, en este escenario, es el mismo iterador dos veces debido al *2azúcar sintáctico de Python. Los iteradores también se ejecutan solo una vez.

Además, ziptoma cualquier número de iterables (las secuencias son iterables ) y crea una tupla a partir del primer elemento de cada una de las secuencias de entrada. Dado que ambos iteradores son idénticos en nuestro caso, zip mueve el mismo iterador dos veces por cada tupla de salida de 2 elementos.

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.

El operador unpacking ( *) asegura que los iteradores se agoten, lo que en este caso es hasta que no haya suficiente entrada para crear una tupla de 2 elementos.

Esto se puede extender a cualquier valor ny zip(*[iter(s)]*n)funciona como se describe.

Akhan
fuente
Perdón por ser lento. Pero, ¿podría explicar "el mismo iterador dos veces debido al azúcar sintáctico de Python * 2. Los iteradores también se ejecutan solo una vez". parte por favor? Si es así, ¿cómo es que el resultado no es [("A", "A") ....]? Gracias.
Bowen Liu
@BowenLiu *es solo una conveniencia para duplicar un objeto. Pruébelo con escalares y luego con listas. Proveedores print(*zip(*[iter("ABCDEFG")]*2))vs print(*zip(*[iter("ABCDEFG"), iter("ABCDEFG")])). Luego comience a dividir los dos en pasos más pequeños para ver cuáles son los objetos iteradores en las dos declaraciones.
Akhan