Як вибрати лише один предмет з генератора?


213

У мене функція генератора:

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, і це суттєво, якщо вам потрібно підтримати 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звичайному.


8
Це занадто багато роботи, щоб просто отримати перший елемент результату списку. Досить часто мені не потрібно, щоб він був ледачим, але не мав вибору в py3. Хіба не щось схоже mySeq.head?
javadba

2

Я не вірю, що існує зручний спосіб отримати довільне значення з генератора. Генератор забезпечить наступний () метод для проходження себе, але повна послідовність не створюється негайно для збереження пам'яті. Ось функціональна різниця між генератором і списком.


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

2
Просто замініть "generator.next ()" на "next (generator)" для Python 3.
iyop45

-3

Я вважаю, що єдиний спосіб - отримати список із ітератора, а потім отримати потрібний елемент із цього списку.

l = list(myfunct())
l[4]

Відповідь Свена, мабуть, краща, але я просто залишу це тут, якщо він більше відповідає вашим потребам.
keegan3d

26
Переконайтеся, що у вас є кінцевий генератор, перш ніж робити це.
Сет

6
Вибачте, це має складність довжини ітератора, тоді як проблема, очевидно, O (1).
йо

1
Витрата занадто багато пам’яті та процесу для висмоктування з генератора! Також, як згадувалося раніше @Seth, генераторам не гарантується, коли припинити генерування.
піловер

Це, очевидно, не єдиний спосіб (або найкращий спосіб, якщо myfunct()генерує велику кількість значень), оскільки ви можете використовувати вбудовану функцію nextдля отримання наступного згенерованого значення.
HelloGoodbye
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.