Как выбрать только один предмет из генератора?

214

У меня есть функция генератора, как показано ниже:

def myfunct():
  ...
  yield result

Обычный способ вызова этой функции:

for r in myfunct():
  dostuff(r)

Мой вопрос, есть ли способ получить только один элемент из генератора, когда мне нравится? Например, я хотел бы сделать что-то вроде:

while True:
  ...
  if something:
      my_element = pick_just_one_element(myfunct())
      dostuff(my_element)
  ...
Александрос
источник

Ответы:

304

Создать генератор с помощью

g = myfunct()

Каждый раз, когда вы хотите товар, используйте

next(g)

(или g.next()в Python 2.5 или ниже).

Если генератор выйдет, он повысится StopIteration. Вы можете перехватить это исключение, если необходимо, или использовать defaultаргумент для next():

next(g, default_value)
Свен Марнах
источник
4
Обратите внимание: он вызывает StopIteration только тогда, когда вы пытаетесь использовать g.next () после предоставления последнего элемента в g.
Wilduck
26
next(gen, default)может также использоваться, чтобы избежать StopIterationисключения. Например, next(g, None)для генератора строк выдает либо строку, либо None после завершения итерации.
Аттила
8
в Python 3000 следующим () является __следующий __ ()
Джонатан Болдуин
27
@JonathanBaldwin: Ваш комментарий несколько вводит в заблуждение. В Python 3 вы бы использовали второй синтаксис, указанный в моем ответе next(g). Это вызов изнутри g.__next__(), но вам не нужно об этом беспокоиться, как обычно вам все равно, что len(a)звонит изнутри a.__len__().
Свен Марнах
14
Я должен был быть более ясным. g.next()находится g.__next__()в py3k. Встроенная система next(iterator)существует начиная с Python 2.6 и должна использоваться во всем новом коде Python, и ее тривиально для backimplement, если вам нужна поддержка py <= 2.5.
Джонатан Болдуин
29

Для выбора только одного элемента генератора используйте breakв forвыражении, илиlist(itertools.islice(gen, 1))

Согласно вашему примеру (буквально) вы можете сделать что-то вроде:

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

Если вы хотите « получить только один элемент из [когда-то сгенерированного] генератора всякий раз, когда мне нравится » (я полагаю, что 50% - это первоначальное и наиболее распространенное намерение), тогда:

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

Таким образом generator.next()можно избежать явного использования , и обработка конца ввода не требует (загадочной) StopIterationобработки исключений или дополнительных сравнений значений по умолчанию.

else:Из forраздела заявления необходимо только , если вы хотите сделать что - то особенное в случае отслуживших генератора.

Примечание на next()/ .next():

В Python3 .next()метод был переименован по .__next__()уважительной причине: он считается низкоуровневым (PEP 3114). До Python 2.6 встроенной функции next()не существовало. И даже обсуждалось переход next()к operatorмодулю (что было бы разумно) из-за его редкой необходимости и сомнительной инфляции встроенных имен.

Использование next()без значения по-прежнему является практикой очень низкого уровня - выбрасывать загадку, StopIterationкак гром среди ясного неба, в обычный код приложения. И использование по next()умолчанию часового - который лучше всего должен быть единственным вариантом для next()прямого builtinsвхода - ограничено и часто дает причину для странной непитонической логики / читабельности.

Итог: использование next () должно быть очень редким - как использование функций operatorмодуля. Используя for x in iterator, islice, list(iterator)и другие функции , принимая итератор легко естественный способ использования итераторов на уровне приложений - и вполне возможно всегда. next()низкоуровневая, лишняя концепция, неочевидная - как показывает вопрос этой темы. Хотя, например, использование breakв forявляется обычным.

KXR
источник
8
Это слишком много работы для получения первого элемента списка результатов. Довольно часто мне не нужно, чтобы это было лениво, но у меня нет выбора в py3. Есть ли что-то похожее mySeq.head?
Джавадба
2

Я не верю, что есть удобный способ извлечь произвольное значение из генератора. Генератор предоставит метод next () для обхода себя, но полная последовательность не создается сразу для экономии памяти. Это функциональная разница между генератором и списком.

gddc
источник
1

Для тех из вас, кто просматривает эти ответы, чтобы получить полный рабочий пример для Python3 ... хорошо, вы идете:

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)

Это выводит:

1001
1002
1003
Дейв Роув
источник
1

Генератор - это функция, которая производит итератор. Поэтому, когда у вас есть экземпляр итератора, используйте next (), чтобы получить следующий элемент из итератора. Например, используйте функцию next () для извлечения первого элемента, а затем используйте for inдля обработки оставшихся элементов:

# 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()

убедитесь, что перехвачено исключение после последнего элемента

Джон
источник
Недействительно для Python 3, см. Отличный ответ от kxr .
клик
2
Просто замените «generator.next ()» на «следующий (генератор)» для Python 3.
iyop45
-3

Я считаю, что единственный способ - получить список от итератора, а затем получить нужный элемент из этого списка.

l = list(myfunct())
l[4]
keegan3d
источник
Ответ Свена, вероятно, лучше, но я просто оставлю это здесь, если оно более соответствует вашим потребностям.
keegan3d
26
Убедитесь, что у вас есть конечный генератор, прежде чем делать это.
Сет
6
К сожалению, это имеет сложность длины итератора, в то время как проблема, очевидно, O (1).
лет»
1
Трата слишком большого количества памяти и процесса, чтобы высосать из генератора! Кроме того, как уже упоминалось @Seth, генераторы не гарантируются, когда прекратить генерацию.
пиловер
Это, очевидно, не единственный способ (или лучший способ, если myfunct()генерируется большое количество значений), поскольку вы можете использовать встроенную функцию nextдля получения следующего сгенерированного значения.
Hellogoodbye