Como zip (* [iter (s)] * n) funciona em 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)]

Como zip(*[iter(s)]*n)funciona? Como seria se fosse escrito com um código mais detalhado?

Oliver Zheng
fonte
1
também dê uma olhada aqui onde como funciona também é explicado: stackoverflow.com/questions/2202461/…
Matt Joiner
se as respostas aqui não forem suficientes, eu coloquei no blog aqui: telliott99.blogspot.com/2010/01/…
telliott99
7
Embora muito intrigante, essa técnica deve ir contra o valor central de "legibilidade" do Python!
Demis

Respostas:

108

iter()é um iterador em uma sequência. [x] * nproduz uma lista contendo a nquantidade de x, ou seja, uma lista de comprimento n, onde cada elemento está x. *argdescompacta uma sequência em argumentos para uma chamada de função. Portanto, você está passando o mesmo iterador 3 vezes para zip()e ele puxa um item do iterador a cada vez.

x = iter([1,2,3,4,5,6,7,8,9])
print zip(x, x, x)
Ignacio Vazquez-Abrams
fonte
1
É bom saber: quando um iterador yields (= returns) um item, você pode imaginar esse item como "consumido". Portanto, na próxima vez que o iterador for chamado, ele produzirá o próximo item "não consumido".
winklerrr
46

As outras ótimas respostas e comentários explicam bem as funções de desempacotamento de argumento e zip () .

Como Ignacio e ujukatzel dizem, você passa para zip()três referências ao mesmo iterador e zip()faz três tuplas dos inteiros - na ordem - de cada referência ao 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
^                    ^                    ^            
      ^                    ^                    ^
            ^                    ^                    ^

E já que você pede um exemplo de código mais detalhado:

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

Seguindo os valores de starte end:

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

FWIW, você pode obter o mesmo resultado map()com um argumento inicial de None:

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

Para obter mais informações sobre zip()e map(): http://muffinresearch.co.uk/archives/2007/10/16/python-transposing-lists-with-map-and-zip/

carne_mecânica
fonte
31

Acho que uma coisa que faltou em todas as respostas (provavelmente óbvia para aqueles familiarizados com os iteradores), mas não tão óbvia para os outros é -

Como temos o mesmo iterador, ele é consumido e os elementos restantes são usados ​​pelo zip. Portanto, se simplesmente usarmos a lista e não o iter, por exemplo.

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 o iterador, exibe os valores e só permanece disponível, portanto, para zip, uma vez que 0 é consumido, 1 fica disponível e então 2 e assim por diante. Uma coisa muito sutil, mas muito inteligente !!!

gabhijit
fonte
+1, você me salvou! Não posso acreditar que outras respostas pularam esse detalhe vital, presumindo que todo mundo saiba disso. Você pode fornecer alguma referência a uma documentação que inclua essas informações?
Snehasish Karmakar,
9

iter(s) retorna um iterador para s.

[iter(s)]*n faz uma lista de n vezes o mesmo iterador para s.

Portanto, ao fazer zip(*[iter(s)]*n)isso, ele extrai um item de todos os três iteradores da lista em ordem. Como todos os iteradores são o mesmo objeto, ele apenas agrupa a lista em partes de n.

Sttwister
fonte
7
Não 'n iteradores da mesma lista', mas 'n vezes o mesmo objeto iterador'. Objetos iteradores diferentes não compartilham estado, mesmo quando estão na mesma lista.
Thomas Wouters
Obrigado, corrigido. Na verdade era isso que eu estava "pensando", mas escrevi outra coisa.
sttwister de
6

Um conselho para usar o zip dessa forma. Isso truncará sua lista se seu comprimento não for uniformemente divisível. Para contornar isso, você pode usar itertools.izip_longest se puder aceitar os valores de preenchimento. Ou você pode usar algo assim:

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

Impressões:

1, 2, 3
4, 5, 6
7, 8, 9
10, 11
Magnusson
fonte
3
Isso já está documentado em itertoolsreceitas: docs.python.org/2/library/itertools.html#recipes grouper . Não há necessidade de reinventar a roda
Jamylak
1

Provavelmente é mais fácil ver o que está acontecendo no interpretador python ou ipythoncom n = 2:

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

Portanto, temos uma lista de dois iteradores que estão apontando para o mesmo objeto iterador. Lembre-se de que iterem um objeto retorna um objeto iterador e, neste cenário, é o mesmo iterador duas vezes devido ao *2açúcar sintático python. Os iteradores também são executados apenas uma vez.

Além disso, zipleva qualquer número de iteráveis ​​( sequências são iteráveis ) e cria tupla a partir do i'ésimo elemento de cada uma das sequências de entrada. Como os dois iteradores são idênticos em nosso caso, zip move o mesmo iterador duas vezes para cada tupla de 2 elementos de saída.

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.

O operador unpacking ( *) garante que os iteradores sejam executados até a exaustão, que neste caso é até que não haja entrada suficiente para criar uma tupla de 2 elementos.

Isso pode ser estendido a qualquer valor de ne zip(*[iter(s)]*n)funciona conforme descrito.

Akhan
fonte
Desculpe por ser lento. Mas você poderia explicar o "mesmo iterador duas vezes devido ao açúcar sintático python * 2. Os iteradores também são executados apenas uma vez". parte por favor? Em caso afirmativo, como o resultado não é [("A", "A") ....]? Obrigado.
Bowen Liu
@BowenLiu *é apenas uma conveniência para duplicar um objeto. Experimente com escalares e depois com listas. Pesquisas relacionadas print(*zip(*[iter("ABCDEFG")]*2))vs print(*zip(*[iter("ABCDEFG"), iter("ABCDEFG")])). Em seguida, comece dividindo os dois em etapas menores para ver quais são os objetos iteradores reais nas duas instruções.
Akhan