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.
É 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".
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:
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 3for 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:
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.
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:
Provavelmente é mais fácil ver o que está acontecendo no interpretador python ou ipythoncom n = 2:
In[35]:[iter("ABCDEFGH")]*2Out[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.
Respostas:
iter()
é um iterador em uma sequência.[x] * n
produz uma lista contendo an
quantidade dex
, ou seja, uma lista de comprimenton
, onde cada elemento estáx
.*arg
descompacta uma sequência em argumentos para uma chamada de função. Portanto, você está passando o mesmo iterador 3 vezes parazip()
e ele puxa um item do iterador a cada vez.fonte
yield
s (=return
s) 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".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 ezip()
faz três tuplas dos inteiros - na ordem - de cada referência ao iterador:E já que você pede um exemplo de código mais detalhado:
Seguindo os valores de
start
eend
:FWIW, você pode obter o mesmo resultado
map()
com um argumento inicial deNone
:Para obter mais informações sobre
zip()
emap()
: http://muffinresearch.co.uk/archives/2007/10/16/python-transposing-lists-with-map-and-zip/fonte
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.
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 !!!
fonte
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 den
.fonte
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:
Uso:
Impressões:
fonte
itertools
receitas: docs.python.org/2/library/itertools.html#recipesgrouper
. Não há necessidade de reinventar a rodaProvavelmente é mais fácil ver o que está acontecendo no interpretador python ou
ipython
comn = 2
:Portanto, temos uma lista de dois iteradores que estão apontando para o mesmo objeto iterador. Lembre-se de que
iter
em um objeto retorna um objeto iterador e, neste cenário, é o mesmo iterador duas vezes devido ao*2
açúcar sintático python. Os iteradores também são executados apenas uma vez.Além disso,
zip
leva 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.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
n
ezip(*[iter(s)]*n)
funciona conforme descrito.fonte
*
é apenas uma conveniência para duplicar um objeto. Experimente com escalares e depois com listas. Pesquisas relacionadasprint(*zip(*[iter("ABCDEFG")]*2))
vsprint(*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.