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

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)

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.


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


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

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)

0
generator = myfunct()
while True:
   my_element = generator.next()

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


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]

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