2013-09-10 5 views
3

У меня есть функция, называемая х, что производит генератор, как это:«выход» в Python

a = 5 
def x(): 
    global a 
    if a == 3: 
     raise Exception("Stop") 
    a = a - 1 
    yield a 

Тогда в оболочке питона я называю эту функцию так:

>>> print x().next() 
>>> 4 
>>> print x().next() 
>>> 3 
>>> print x().next() 
>>> <python-input-112-f3c02bba26c8> in x() 
      2  global a 
      3  if a == 3: 
    ----> 4   raise Exception 
      5  a = a - 1 
      6  yield a 

    Exception: 

Однако , когда я называю эту функцию и назначаю ее переменной, она ведет себя по-разному:

>>> a = 5 
>>> b = x() 
>>> print b.next() 
>>> 4 
>>> print b.next() 
>>> ----> 1 b.next() 
    StopIteration: 

Как это возможно? Не следует ли распечатывать и поднять StopIteration на следующей итерации?

PS: Я знаю, что при первом вызове функции корпус не запускается, просто производит генератор. То, что я не понял, - это то, что меняется, если я вызываю и назначаю его переменной?

ответ

13

В первом примере, вы создавали генератор нового каждый раз:

x().next() 

Это запускает генератор от верхнего, поэтому первое утверждение. Когда a == 3, возникает исключение, в противном случае генератор просто дает и паузы.

Когда вы назначили генератор позже, глобальный a начал в 5, код затем продолжал откуда он вышел из него, пока не закончится или попадается другой yield заявления, то закончился. Когда функция генератора заканчивается, он поднимает StopIteration.

Давайте разберем это на шаги:

  1. a = 5.
  2. Вы создаете новый генератор и вызываете на нем .next(). Выполняется следующий код:

    global a 
    if a == 3: # False 
        raise Exception("Stop") 
    a = a - 1 # a is now 4 
    yield a 
    

    генератор остановился на последней строке, и 4 это дало.

  3. Создайте новый генератор и позвоните по телефону .next(). a является 4 в начале:

    global a 
    if a == 3: # False 
        raise Exception("Stop") 
    a = a - 1 # a is now 3 
    yield a 
    

    Генератор паузы на последней строке, и 3 это дало.

  4. Создайте новый генератор и позвоните по телефону .next(). a является 3 в начале:

    global a 
    if a == 3: # True 
        raise Exception("Stop") 
    

    Исключение повышается.

  5. Вы снова установили a = 5.

  6. Создайте новый генератор, сохраните ссылку в b и позвоните по телефону .next(). Выполняется следующий код:

    global a 
    if a == 3: # False 
        raise Exception("Stop") 
    a = a - 1 # a is now 4 
    yield a 
    

    генератор остановился на последней строке, и 4 это дало.

  7. Вы называете .next()снова на том же, существующий генератор, на который ссылается b. Код возобновляет в пункте паузы.

    Функция не имеет больше кода в этой точке и возвращается. StopIteration поднят.

Если вы должны были использовать петлю вместо этого, вы бы увидели разницу лучше:

>>> def looping(stop): 
... for i in looping(stop): 
...  yield i 
... 
>>> looping(3).next() 
0 
>>> looping(3).next() 
0 

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

>>> stored = looping(3) 
>>> stored.next() 
0 
>>> stored.next() 
1 
>>> stored.next() 
2 
>>> stored.next() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
StopIteration 

Во время цикла, каждый раз, когда выражение yield выполняется, код остановился; вызывающий .next() продолжает функцию, в которой он остался от предыдущего времени.

Исключение составляет полностью нормальное; как генераторы сообщают, что они сделаны. for цикл ищет это исключение для завершения цикла:

>>> for i in looping(3): 
...  print i 
... 
0 
1 
2 
+0

OMG, как вы можете написать весь этот текст за 3 минуты? – Matthias

+0

@Matthias: Я быстрый типер, но вы также получаете до 5 минут грациозности, чтобы добавить к сообщению без последнего отредактированного обновления времени. –

+0

+1 Отличное объяснение. –

0

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

>>> def a(): 
... for x in range(5): 
...  yield x 
... 
>>> a() 
<generator object a at 0xb7f0a9b4> 
>>> list(a()) 
[0, 1, 2, 3, 4] 

Вы обычно хотите использовать выход внутри цикла, и он имеет очень четкое поведение возвращает значение, а потом возобновить цикл.

Если ваш пример, х всегда возвращает генератор, который будет производить только один элемент. В первом примере вы вызываете x несколько раз, поэтому получаете несколько результатов. Во втором примере, когда вы назначаете его переменной, вы вызываете ее только один раз, поэтому вы получаете только один результат.

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

Paul