Jak działa zip (* [iter (y)] * n) w Pythonie?

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

Jak to zip(*[iter(s)]*n)działa? Jak by to wyglądało, gdyby zostało napisane z bardziej szczegółowym kodem?

Oliver Zheng
źródło
1
zajrzyj również tutaj, gdzie wyjaśniono również, jak to działa: stackoverflow.com/questions/2202461/…
Matt Joiner
jeśli odpowiedzi tutaj nie są wystarczające, zamieściłem je na blogu: telliott99.blogspot.com/2010/01/…
telliott99
7
Chociaż jest to bardzo intrygujące, technika ta musi być sprzeczna z podstawową wartością „czytelności” Pythona!
Demis

Odpowiedzi:

109

iter()jest iteratorem po sekwencji. [x] * ntworzy listę zawierającą nilość x, czyli listę długości n, w której znajduje się każdy element x. *argrozpakowuje sekwencję na argumenty dla wywołania funkcji. Dlatego przekazujesz ten sam iterator 3 razy do zip()i za każdym razem pobiera on element z iteratora.

x = iter([1,2,3,4,5,6,7,8,9])
print zip(x, x, x)
Ignacio Vazquez-Abrams
źródło
1
Warto wiedzieć: kiedy iterator yields (= returns) elementem, możesz sobie wyobrazić ten przedmiot jako „zużyty”. Więc następnym razem, gdy iterator jest wywoływany, zwraca następny „niewykorzystany” element.
winklerrr
46

Inne świetne odpowiedzi i komentarze dobrze wyjaśniają rolę rozpakowywania argumentów i zip () .

Jak mówią Ignacio i Ujukatzel , przechodzisz do zip()trzech odniesień do tego samego iteratora i tworzyszzip() 3-krotki liczb całkowitych - po kolei - z każdego odniesienia do iteratora:

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

A ponieważ prosisz o bardziej szczegółowy przykład kodu:

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

Zgodnie z wartościami starti end:

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

FWIW, możesz uzyskać ten sam wynik z map()początkowym argumentem None:

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

Więcej na zip()i map(): http://muffinresearch.co.uk/archives/2007/10/16/python-transposing-lists-with-map-and-zip/

mięso_mechaniczne
źródło
31

Myślę, że jedna rzecz, której brakuje we wszystkich odpowiedziach (prawdopodobnie oczywista dla osób znających iteratory), ale nie jest tak oczywista dla innych, to -

Ponieważ mamy ten sam iterator, zostaje on zużyty, a pozostałe elementy są używane przez zip. Jeśli więc po prostu skorzystaliśmy z listy, a nie z iter, np.

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

Użycie iteratora powoduje wyskakiwanie wartości i pozostanie tylko dostępnymi, więc dla zip, gdy zostanie zużyte 0, dostępne jest 1, a następnie 2 i tak dalej. Bardzo subtelna rzecz, ale całkiem sprytna !!!

gabhijit
źródło
+1, uratowałeś mnie! Nie mogę uwierzyć, że inne odpowiedzi pominęły ten istotny szczegół, zakładając, że wszyscy o tym wiedzą. Czy może Pan podać jakieś odniesienie do dokumentacji, która zawiera te informacje?
Snehasish Karmakar
9

iter(s) zwraca iterator dla s.

[iter(s)]*n tworzy listę n razy taką samą iterator dla s.

Tak więc, kiedy to robi zip(*[iter(s)]*n), wyodrębnia element ze wszystkich trzech iteratorów z listy po kolei. Ponieważ wszystkie iteratory są tym samym obiektem, po prostu grupuje listę w fragmenty n.

sttwister
źródło
7
Nie „n iteratorów tej samej listy”, ale „n razy ten sam obiekt iteratora”. Różne obiekty iteratora nie mają wspólnego stanu, nawet jeśli należą do tej samej listy.
Thomas Wouters
Dzięki, poprawione. Rzeczywiście, o tym „myślałem”, ale napisałem coś innego.
sttwister
6

Jedna rada dotycząca korzystania z zip w ten sposób. Lista zostanie obcięta, jeśli jej długość nie jest podzielna. Aby obejść ten problem, możesz użyć itertools.izip_longest, jeśli akceptujesz wartości wypełnienia. Lub możesz użyć czegoś takiego:

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

Stosowanie:

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

Wydruki:

1, 2, 3
4, 5, 6
7, 8, 9
10, 11
jmagnusson
źródło
3
Jest to już udokumentowane w itertoolsprzepisach: docs.python.org/2/library/itertools.html#recipes grouper . Nie ma potrzeby
odkrywania
1

Prawdopodobnie łatwiej jest zobaczyć, co się dzieje w interprecie Pythona lub ipythonz n = 2:

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

Mamy więc listę dwóch iteratorów, które wskazują na ten sam obiekt iteratora. Pamiętaj, że iterna obiekcie zwraca obiekt iteratora, aw tym scenariuszu jest to ten sam iterator dwa razy ze względu na *2cukier składniowy Pythona. Iteratory również działają tylko raz.

Ponadto zippobiera dowolną liczbę iterowalnych ( sekwencjeiterowalne ) i tworzy krotkę z i-tego elementu każdej z sekwencji wejściowych. Ponieważ oba iteratory są identyczne w naszym przypadku, zip przesuwa ten sam iterator dwa razy dla każdej 2-elementowej krotki wyjścia.

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.

Operator unpacking ( *) zapewnia, że ​​iteratory działają do wyczerpania, co w tym przypadku ma miejsce do momentu, gdy nie ma wystarczającej ilości danych wejściowych, aby utworzyć 2-elementową krotkę.

Można to rozszerzyć na dowolną wartość ni zip(*[iter(s)]*n)działa zgodnie z opisem.

akhan
źródło
Przepraszam za powolność. Ale czy mógłbyś wyjaśnić "ten sam iterator dwa razy z powodu cukru składniowego * 2 Pythona. Iteratory również działają tylko raz". część proszę? Jeśli tak, dlaczego wynik nie jest [(„A”, „A”)…]? Dzięki.
Bowen Liu
@BowenLiu *to po prostu wygoda kopiowania obiektu. Wypróbuj to ze skalarami, a następnie z listami. Spróbuj także print(*zip(*[iter("ABCDEFG")]*2))vs print(*zip(*[iter("ABCDEFG"), iter("ABCDEFG")])). Następnie zacznij rozdzielać te dwa na mniejsze kroki, aby zobaczyć, jakie są faktycznie obiekty iteratora w dwóch instrukcjach.
akhan