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?


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)

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/


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


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


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

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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.