Jak wybrać tylko jeden przedmiot z generatora?

213

Mam funkcję generatora, jak poniżej:

def myfunct():
  ...
  yield result

Zwykłym sposobem wywołania tej funkcji byłoby:

for r in myfunct():
  dostuff(r)

Moje pytanie: czy istnieje sposób na uzyskanie tylko jednego elementu z generatora, kiedy tylko chcę? Na przykład chciałbym zrobić coś takiego:

while True:
  ...
  if something:
      my_element = pick_just_one_element(myfunct())
      dostuff(my_element)
  ...
Alexandros
źródło

Odpowiedzi:

304

Utwórz generator za pomocą

g = myfunct()

Za każdym razem, gdy chcesz przedmiot, użyj

next(g)

(lub g.next()w Python 2.5 lub niższym).

Jeśli generator wyjdzie, podniesie się StopIteration. W razie potrzeby możesz złapać ten wyjątek lub użyć defaultargumentu, aby next():

next(g, default_value)
Sven Marnach
źródło
4
Zauważ, że podniesie StopIteration tylko wtedy, gdy spróbujesz użyć g.next () po dostarczeniu ostatniego elementu wg.
Wilduck
26
next(gen, default)można również użyć, aby uniknąć StopIterationwyjątku. Na przykład next(g, None)dla generatora ciągów albo po zakończeniu iteracji albo powstanie ciąg, albo Żaden.
Attila,
8
w Pythonie 3000, next () to __next __ ()
Jonathan Baldwin
27
@JathanathanBaldwin: Twój komentarz jest nieco mylący. W Pythonie 3, należy użyć drugiego składni podanej w moim odpowiedź next(g). To będzie wewnętrznie zadzwonić g.__next__(), ale tak naprawdę nie trzeba się martwić o to, jak zwykle nie obchodzi, że len(a)wewnętrznie połączeń a.__len__().
Sven Marnach,
14
Powinienem być bardziej dokładny. g.next()jest g.__next__()w py3k. Wbudowane next(iterator)jest już od Pythona 2.6 i jest tym, co powinno być używane w całym nowym kodzie Pythona, a implementacja wsteczna jest łatwa, jeśli potrzebujesz obsługiwać py <= 2.5.
Jonathan Baldwin,
29

Do wybrania tylko jednego elementu generatora użyj breakw forinstrukcji lublist(itertools.islice(gen, 1))

Zgodnie z twoim przykładem (dosłownie) możesz zrobić coś takiego:

while True:
  ...
  if something:
      for my_element in myfunct():
          dostuff(my_element)
          break
      else:
          do_generator_empty()

Jeśli chcesz „ dostać tylko jeden element z generatora [raz wygenerowanego] , kiedy tylko chcę ” (przypuszczam, że 50% to pierwotna intencja i najczęstsza intencja):

gen = myfunct()
while True:
  ...
  if something:
      for my_element in gen:
          dostuff(my_element)
          break
      else:
          do_generator_empty()

W ten sposób generator.next()można uniknąć jawnego użycia , a obsługa końca wejścia nie wymaga (tajemniczej) StopIterationobsługi wyjątków lub dodatkowych porównań wartości domyślnych.

else:Z forsekcji oświadczenie jest potrzebne tylko wtedy, gdy chcesz zrobić coś specjalnego w przypadku wycofanych z generatora.

Uwaga na next()/ .next():

W Python3 .next()zmieniono nazwę tej metody .__next__()na dobry powód: uważany za niski poziom (PEP 3114). Przed Pythonem 2.6 wbudowana funkcja next()nie istniała. Dyskutowano nawet o przejściu next()do operatormodułu (co byłoby mądre) ze względu na jego rzadką potrzebę i wątpliwą inflację wbudowanych nazw.

Używanie next()bez domyślnych ustawień jest nadal bardzo niskim poziomem praktyki - rzucanie zagadkami StopIterationjak grom z jasnego nieba w normalnym kodzie aplikacji otwarcie. Korzystanie next()z domyślnego wartownika - który najlepiej powinien być jedyną opcją dla next()bezpośredniego builtinswejścia - jest ograniczone i często daje powód dziwnej nie pytonicznej logiki / zdolności odczytu.

Konkluzja: Używanie next () powinno być bardzo rzadkie - podobnie jak korzystanie z funkcji operatormodułu. Korzystanie for x in iterator, islice, list(iterator)oraz inne funkcje przyjmowania iterator bezproblemowo jest naturalnym sposobem korzystania iteratory na poziomie aplikacji - i dość zawsze możliwe. next()jest niski poziom, dodatkowa koncepcja, nieoczywista - jak pokazuje pytanie tego wątku. Podczas gdy np. Używanie breakw forjest konwencjonalne.

kxr
źródło
8
Jest to zdecydowanie zbyt dużo pracy, aby uzyskać pierwszy element wyniku listy. Często nie muszę być leniwy, ale nie mam wyboru w py3. Czy nie ma czegoś podobnego mySeq.head?
javadba
2

Nie sądzę, aby istniał wygodny sposób na uzyskanie dowolnej wartości z generatora. Generator zapewni następną () metodę przejścia przez siebie, ale pełna sekwencja nie jest tworzona natychmiast, aby zaoszczędzić pamięć. Na tym polega funkcjonalna różnica między generatorem a listą.

gddc
źródło
1

Dla tych, którzy przeglądają te odpowiedzi w celu uzyskania kompletnego przykładu dla Python3 ... cóż, proszę:

def numgen():
    x = 1000
    while True:
        x += 1
        yield x

nums = numgen() # because it must be the _same_ generator

for n in range(3):
    numnext = next(nums)
    print(numnext)

To daje:

1001
1002
1003
Dave Rove
źródło
1

Generator to funkcja tworząca iterator. Dlatego po utworzeniu instancji iteratora użyj next (), aby pobrać następny element z iteratora. Na przykład użyj funkcji next (), aby pobrać pierwszy element, a następnie użyj for indo przetworzenia pozostałych elementów:

# create new instance of iterator by calling a generator function
items = generator_function()

# fetch and print first item
first = next(items)
print('first item:', first)

# process remaining items:
for item in items:
    print('next item:', item)
andrii
źródło
0
generator = myfunct()
while True:
   my_element = generator.next()

upewnij się, że złapałeś wyjątek zgłoszony po zajęciu ostatniego elementu

Jan
źródło
Nie dotyczy Pythona 3, zobacz doskonałą odpowiedź autorstwa kxr .
clacke
2
Po prostu zamień „generator.next ()” na „next (generator)” dla Python 3.
iyop45
-3

Uważam, że jedynym sposobem jest uzyskanie listy z iteratora, a następnie pobranie żądanego elementu z tej listy.

l = list(myfunct())
l[4]
keegan3d
źródło
Odpowiedź Svena jest prawdopodobnie lepsza, ale zostawię to tutaj, ponieważ jest bardziej dostosowane do twoich potrzeb.
keegan3d
26
Zanim to zrobisz, upewnij się, że masz skończony generator.
Seth
6
Niestety, ma to złożoność długości iteratora, podczas gdy problemem jest oczywiście O (1).
jo
1
Marnowanie zbyt dużej ilości pamięci i procesu do zasysania z generatora! Ponadto, jak wspomniano wcześniej @Seth, nie ma gwarancji, kiedy generatory przestaną generować.
pylover
To oczywiście nie jedyny sposób (czy to najlepszy sposób, jeśli myfunct()generuje dużą liczbę wartości), ponieważ można skorzystać z wbudowanej funkcji next, aby uzyskać kolejną wygenerowane wartości.
HelloGoodbye,