2013-05-09 2 views
14

Почему в примере функции завершается:Как выход исключает исключение StopIteration?

def func(iterable): 
    while True: 
     val = next(iterable) 
     yield val 

но если я отрываюсь выход функция оператора поднимет StopIteration исключение?

EDIT: Извините, что вы вводите вас в заблуждение. Я знаю, что такое генераторы и как их использовать. Конечно, когда я сказал, что функция прекращается, я не имел в виду нетерпеливую оценку функции. Я просто подразумевал, что, когда я использую функцию для создания генератора:

gen = func(iterable) 

в случае FUNC он работает и возвращает тот же генератор, но в случае func2:

def func2(iterable): 
    while True: 
     val = next(iterable) 

он поднимает StopIteration вместо от Нет возврат или бесконечный цикл.

Позвольте мне уточнить. Существует функция tee в itertools что эквивалентно:

def tee(iterable, n=2): 
    it = iter(iterable) 
    deques = [collections.deque() for i in range(n)] 
    def gen(mydeque): 
     while True: 
      if not mydeque:    # when the local deque is empty 
       newval = next(it)  # fetch a new value and 
       for d in deques:  # load it to all the deques 
        d.append(newval) 
      yield mydeque.popleft() 
    return tuple(gen(d) for d in deques) 

Существует, на самом деле, какая-то магия, потому что вложенная функция поколения имеет бесконечный цикл без заявлений перерыва. gen функция прекращается из-за StopIteration исключение, если нет элементов в it. Но он заканчивается корректно (без привлечения исключений), т. Е. Просто останавливает цикл. Итак, вопрос: где StopIteration обрабатывается?

+0

Как вы это называете? –

ответ

24

Чтобы ответить на ваш вопрос о том, где StopIteration попадает в генератор gen, созданный внутри itertools.tee: это не так. Это зависит от потребителя результатов tee, чтобы поймать исключение по мере его повторения.

Прежде всего важно отметить, что функция генератора (которая является любой функцией с инструкцией yield в ней в любом месте) принципиально отличается от нормальной. Вместо того, чтобы запускать код функции, когда он вызывается, вместо этого вы просто получите объект generator, когда вы вызываете функцию. Только когда вы будете перебирать генератор, вы запустите код.

Функция генератора никогда не завершит итерацию без повышения StopIteration (если вместо этого не возникает другое исключение). StopIteration - это сигнал от генератора, что он сделан, и он не является обязательным. Если вы достигнете утверждения return или кода кода генератора, не поднимая ничего, Python поднимет для вас StopIteration!

Это отличается от обычных функций, которые возвращают None, если они достигают конца, не возвращая ничего. Он связан с различными способами работы генераторов, как я описал выше.

Вот функция генератора пример, который будет легко увидеть, как StopIteration получает поднятый:

def simple_generator(): 
    yield "foo" 
    yield "bar" 
    # StopIteration will be raised here automatically 

Вот что происходит, когда вы потребляете его:

>>> g = simple_generator() 
>>> next(g) 
'foo' 
>>> next(g) 
'bar' 
>>> next(g) 
Traceback (most recent call last): 
    File "<pyshell#6>", line 1, in <module> 
    next(g) 
StopIteration 

Вызов simple_generator всегда возвращает generator объект немедленно (без запуска какого-либо кода в функции). Каждый вызов next на объекте генератора запускает код до следующего оператора yield и возвращает полученное значение. Если больше нет, то поднимается StopIteration.

Теперь, как правило, вы не видите StopIteration исключений. Причина этого заключается в том, что вы обычно потребляете генераторы внутри лупов for. Заявление for будет автоматически звонить по номеру next снова и снова до StopIteration. Он поймает и подавит исключение StopIteration для вас, поэтому вам не нужно возиться с блоками try/except, чтобы справиться с ним.

for петля как for item in iterable: do_suff(item) почти точно соответствует этому while петле (единственное отличие в том, что реальная for не нужна временная переменная для хранения итератора):

iterator = iter(iterable) 
try: 
    while True: 
     item = next(iterator) 
     do_stuff(item) 
except StopIteration: 
    pass 
finally: 
    del iterator 

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

Не связанный с основным вопросом, есть еще одна вещь, которую я хочу указать. В вашем коде вы вызываете next по переменной iterable. Если вы берете это имя в качестве документации для того, какой тип объекта вы получите, это не обязательно безопасно.

next является частью протокола iterator, а не протокола iterable (или container). Он может работать для некоторых типов итераций (таких как файлы и генераторы, поскольку эти типы являются их собственными итераторами), но он будет терпеть неудачу для других итераций, таких как кортежи и списки. Более правильный подход - позвонить по номеру iter на свой номер iterable, затем позвонить по номеру next на итераторе, который вы получите. (Или просто использовать for петли, которые называют как iter и next для вас в нужное время!)

Edit: Я только что нашел мой собственный ответ в поиске Google для связанного с этим вопрос, и я думал, что обновление до точки что ответ выше не будет полностью верным в будущих версиях Python. PEP 479 делает ошибку, позволяющую StopIteration вымываться из функции генератора. Если это произойдет, Python превратит его в исключение RuntimeError.

Это означает, что код, подобный примерам в itertools, который использует StopIteration для выхода из функции генератора, необходимо будет изменить. Обычно вам нужно поймать исключение с помощью try/except, а затем выполните return.

Поскольку это несовместимое изменение в обратном направлении, оно постепенно изменяется. В Python 3.5 весь код будет работать по-прежнему по умолчанию, но вы можете получить новое поведение с помощью from __future__ import generator_stop. В Python 3.6 код все равно будет работать, но он даст предупреждение. В Python 3.7 новое поведение будет применяться все время.

+0

Итак, StopIteration потребляется определением функции (или, что то же самое, структурой генератора)? Я просто хочу выяснить, будем ли мы использовать следующий за пределами тела функции, он будет увеличивать исключение, но если мы будем использовать внутреннюю функцию, он будет нормально завершен. –

+1

@BranAlgue Нет, определение функции не будет использовать исключение. Как и любое другое исключение, «StopIteration» будет подниматься до стека вызовов, пока он не будет пойман явным блоком 'try' /' catch' или неявным внутри цикла 'for'. Я думаю, что вам не хватает того, что 'StopIteration' не является проблемой в функции генератора. Ожидается, что вы поднимете его, когда вам нечего уступать. Вы можете сделать это явно с помощью 'raise StopIteration()' или неявно, дойдя до конца функции, или вы можете позволить 'StopIteration', созданный вызовом' next', пойти неотображаемым. – Blckknght

+0

Я понимаю это. Я не понимаю, почему 'StopIteration' не проблема внутри функции генератора. Является ли утверждение, что генератор неявно обрабатывает исключение правильно? –

2

Без yield вы проследуете по всему iterable, не останавливаясь, чтобы что-либо сделать с val. Цикл while не охватывает исключение StopIteration. Эквивалентно for цикл будет:

def func(iterable): 
    for val in iterable: 
     pass 

который не поймать StopIteration и просто выйти из цикла и, таким образом вернуться из функции.

Вы можете явно поймать исключение:

def func(iterable): 
    while True: 
     try: 
      val = next(iterable) 
     except StopIteration: 
      break 
5

Когда функция содержит yield, называя это на самом деле не выполнять ничего, он просто создает объект генератора. Выполнение кода будет выполнять только итерация по этому объекту. Поэтому я предполагаю, что вы просто вызываете функцию, а это значит, что функция не поднимает StopIteration, потому что она никогда не выполняется.

Учитывая ваши функции и итератора:

def func(iterable): 
    while True: 
     val = next(iterable) 
     yield val 

iterable = iter([1, 2, 3]) 

Это неправильный способ назвать его:

func(iterable) 

Это правильный путь:

for item in func(iterable): 
    # do something with item 

Вы также может хранить генератор в переменной и называть его next() (или итерации o Ver его каким-либо другим способом):

gen = func(iterable) 
print(next(gen)) # prints 1 
print(next(gen)) # prints 2 
print(next(gen)) # prints 3 
print(next(gen)) # StopIteration 

Кстати, лучший способ, чтобы написать функции выглядит следующим образом:

def func(iterable): 
    for item in iterable: 
     yield item 

Или в Python 3.3 и более поздних версий:

def func(iterable): 
    yield from iter(iterable) 

Конечно, настоящие генераторы редко так тривиальны. :-)

+0

Осторожно с вашим последним примером. 'list' не имеет метода' next' или '__next__', ваш пример не будет работать. Исправить это легко. 'gen = func (iter ([1,2,3])) – mgilson

+0

Да, просто понял, что исправлено. :-) – kindall

0

yield не улавливает StopIteration. Что делает yield для вашей функции, это приводит к тому, что она становится функцией генератора, а не обычной функцией. Таким образом, объект, возвращаемый из вызова функции, является итерируемым объектом (который вычисляет следующее значение, когда вы запрашиваете его с помощью функции next (которая получает неявное выражение в цикле for)). Если вы оставите заявление yield, то питон выполнит весь цикл while, который заканчивается исчерпывающим итерируемым (если он конечно) и поднимает StopIteration сразу, когда вы его вызываете.

считает:

x = func(x for x in []) 
next(x) #raises StopIteration 

for петля перехватывает исключение - Вот как он знает, когда прекратить называть next на итерации вы дали.

Смежные вопросы