2012-06-26 10 views
10

Pisałem odpowiedź na this question, gdy zauważyłem, że moja prosta implementacja nie przyniosła poprawnych wyników. Podczas polowania dół błąd, zauważyłem następujące:Dlaczego zip() upuszcza wartości mojego generatora?

In [1]: import itertools 
In [2]: gen = itertools.cycle((0,1,2)) 

In [3]: zip(gen, range(3)) 
Out[3]: [(0, 0), (1, 1), (2, 2)] 

In [4]: zip(gen, range(3)) 
Out[4]: [(1, 0), (2, 1), (0, 2)] 

z jakiegokolwiek powodu, gen „s next() metoda nazywa się jeden raz additioinal. Aby to zilustrować, użyłem następujących czynności:

class loudCycle(itertools.cycle): 
    def next(self): 
     n = super(loudCycle, self).next() 
     print n 
     return n 

In [6]: gen = loudCycle((0,1,2)) 
In [7]: zip(gen, range(3)) 
0 
1 
2 
0 
Out[7]: [(0, 0), (1, 1), (2, 2)] 

Odpowiedz

17

Dzieje się tak dlatego zip ocenia iteratory from left to right, co oznacza, że ​​po trzech etapach, wywołuje next() na gen a dopiero potem na iter(range(3)) (czy coś takiego) i spotkań a StopIteration. Aby obejść ten problem, należy użyć krótszy (skończone) iterable w lewej skrajnej argumentu:

In [8]: zip(range(3), gen) 
0 
1 
2 
Out[8]: [(0, 0), (1, 1), (2, 2)] 
7

Your self-answer jest dokładnie rację, i prezentuje bardzo dobre rozwiązanie - jeśli jeden z argumentów jest zawsze do zip krótszy niż drugi. Jednak w sytuacjach, w których nie wiesz, która wersja będzie krótsza, przydatne może okazać się islice. islice zapewnia także łatwe obejście, jeśli chcesz, aby pierwszy element w krotkach pochodzi z generatora. W twoim przypadku, można to zrobić:

>>> import itertools 
>>> gen = itertools.cycle(('a', 'b', 'c')) 
>>> seq = range(3) 
>>> zip(itertools.islice(gen, len(seq)), seq) 
[('a', 0), ('b', 1), ('c', 2)] 
>>> zip(itertools.islice(gen, len(seq)), seq) 
[('a', 0), ('b', 1), ('c', 2)] 

Twoja odpowiedź jest chyba lepiej w tym przypadku - to na pewno prostsze - ale myślałem, że to dodać jako dodatek.

Powiązane problemy