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?


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)

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/


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


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


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

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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.