[Python] "yield"키워드는 무엇을합니까?


Answers

Grokking yield 바로 가기

yield 문을 사용하여 함수를 볼 때 다음과 같은 일들을 이해하기 위해이 쉬운 트릭을 적용하십시오.

  1. 함수의 처음에 line result = [] 를 삽입하십시오.
  2. result.append(expr)yield expr 을 대체하십시오.
  3. 함수의 맨 아래에 return result 삽입하십시오.
  4. 예 - yield 진술은 더 이상 필요하지 않습니다! 코드를 읽고 알아 내십시오.
  5. 기능을 원래 정의와 비교하십시오.

이 트릭은 함수 뒤에있는 논리에 대한 아이디어를 줄 수 있지만 실제로 yield 에 따라 달라지는 것은리스트 기반 접근에서 발생하는 것과 상당히 다릅니다. 많은 경우 수율 접근 방식이 훨씬 더 효율적이고 빠른 메모리가 될 것입니다. 다른 경우에는 원래 기능이 제대로 작동하더라도이 트릭을 사용하면 무한 루프에 빠질 수 있습니다. 자세히 알아 보려면 계속 읽어보십시오 ...

반복문, 반복자 및 생성자를 혼동하지 마십시오.

첫째, 반복자 프로토콜 - 당신이 쓸 때

for x in mylist:
    ...loop body...

파이썬은 다음 두 단계를 수행합니다.

  1. mylist 의 반복자를 가져 mylist .

    iter(mylist) -> this는 next() 메서드 (또는 Python 3에서 __next__() 를 가진 객체를 반환합니다.

    [이것은 대부분의 사람들이 당신에게 말하는 것을 잊어 버리는 단계입니다]

  2. 반복기를 사용하여 항목을 반복합니다.

    1 단계에서 반환 된 반복자에서 next() 메서드를 계속 호출합니다. next() 의 반환 값이 x 할당되고 루프 본문이 실행됩니다. next() 내에서 StopIteration 예외가 발생하면 반복기에 값이 더 이상없고 루프가 종료되었음을 의미합니다.

진실은 파이썬이 객체의 내용을 반복 할 때마다 위의 두 단계를 수행하므로 for 루프 일 수 있지만 otherlist.extend(mylist) 와 같은 코드 일 수도 있습니다 ( otherlist 는 Python 목록) .

여기 mylist 는 iterator 프로토콜을 구현하기 때문에 iterable 이다. 사용자 정의 클래스에서 __iter__() 메서드를 구현하여 클래스의 인스턴스를 반복 가능하게 만들 수 있습니다. 이 메소드는 반복자를 리턴해야 합니다 . iterator는 next() 메서드가있는 객체입니다. 같은 클래스에서 __iter__()next() 를 모두 구현하고 __iter__()self 반환하도록 할 수 있습니다. 이것은 단순한 경우에는 효과가 있지만 같은 객체에 두 개의 반복자가 동시에 반복되도록하려는 경우에는 사용할 수 없습니다.

이것이 iterator 프로토콜입니다. 많은 객체가이 프로토콜을 구현합니다.

  1. 붙박이 명부, 사전, 튜플, 세트, ​​파일.
  2. __iter__() 를 구현하는 사용자 정의 클래스.
  3. 발전기.

for 루프는 어떤 종류의 객체를 처리 하는지를 알지 못한다. iterator 프로토콜을 따르기 만하면 next() 호출 할 때 항목 뒤에 항목을 가져 오는 것이 좋다. 내장 목록은 항목을 하나씩 반환하고, 사전은 하나씩 키를 반환하고, 파일은 줄을 하나씩 반환합니다. 그리고 생성자가 반환합니다 ... 결과가 yield 됩니다.

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

yield 문 대신 f123()return 문이 세 개인 경우 첫 번째 문이 실행되고 함수는 종료됩니다. 그러나 f123() 은 일반적인 함수가 아닙니다. f123() 이 호출되면 yield 문에 값을 반환 하지 않습니다 ! 생성자 객체를 반환합니다. 또한 함수는 실제로 종료되지 않습니다. 일시 중지 상태가됩니다. for 루프가 생성자 객체 for 반복하려고하면 함수는 이전에 반환 된 yield 다음 바로 다음 행에서 일시 중단 된 상태에서 다시 시작하고 다음 줄의 코드 (이 경우 yield 문)를 실행하고 다음과 같이 반환합니다. 다음 항목. 이것은 함수가 종료 할 때까지 발생합니다.이 시점에서 생성기는 StopIteration 발생시키고 루프가 종료됩니다.

따라서 생성자 객체는 어댑터와 비슷합니다. 한쪽 끝에서 __iter__()next() 메서드를 노출하여 for 루프를 행복하게 유지하면서 iterator 프로토콜을 보여줍니다. 그러나 다른 쪽에서는 함수를 실행하여 다음 값을 꺼내 정지 모드로 되돌립니다.

왜 발전기를 사용합니까?

일반적으로 생성자를 사용하지 않지만 동일한 로직을 구현하는 코드를 작성할 수 있습니다. 하나의 옵션은 이전에 언급 한 '트릭'임시 목록을 사용하는 것입니다. 예를 들어 무한 루프가 있거나 정말 긴 목록을 가지고있을 때 비효율적 인 메모리 사용을하는 경우가 있습니다. 다른 접근법은 인스턴스 멤버에 상태를 유지하고 next() (또는 Python 3에서 __next__() ) 메소드에서 다음 논리적 단계를 수행하는 새로운 iterable 클래스 SomethingIter 를 구현하는 SomethingIter 입니다. 논리에 따라 next() 메서드 내부의 코드가 매우 복잡해지고 버그가 발생하기 쉽습니다. 여기서 발전기는 깨끗하고 쉬운 솔루션을 제공합니다.

Question

파이썬에서 yield 키워드를 사용하는 것은 무엇입니까? 그것은 무엇을합니까?

예를 들어,이 코드 1 을 이해하려고합니다.

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

그리고 이것은 호출자입니다.

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

_get_child_candidates 메소드가 호출되면 어떻게됩니까? 목록이 반환 되었습니까? 단일 요소? 다시 부르니? 후속 통화는 언제 중단됩니까?

1.이 코드는 Jochen Schulz (jrschulz)가 작성한 것으로, 누가 통계 공간을위한 훌륭한 Python 라이브러리를 만든 것입니다. 이것은 전체 소스에 대한 링크입니다 : Module mspace .




yield 키워드는 두 가지 간단한 사실로 축소되었습니다.

  1. 컴파일러가 함수 내에서 yield 키워드를 발견하면 해당 함수는 더 이상 return 문을 통해 반환되지 않습니다. 대신 생성기라고하는 게으른 "보류 목록"개체를 즉시 반환합니다.
  2. 생성기는 반복 가능합니다. 반복 가능 이란 무엇입니까? 특정 순서로 각 요소를 방문하기위한 프로토콜내장list 또는 set 또는 range 또는 견본보기와 같은 것입니다.

요컨대 , 생성기는 게으르고 점진적으로 보류 된 목록 이며 yield 문을 사용하면 함수 표기법을 사용 하여 생성기가 점차적으로 침을 뱉어 야 하는 목록 값을 프로그래밍 할 수 있습니다 .

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

파이썬의 range 와 같은 makeRange 함수를 정의합시다. makeRange(n) 호출 makeRange(n) 반환 :

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

생성자가 보류중인 값을 즉시 반환하도록하려면 list() 반복 가능한 것처럼 list() 전달할 수 있습니다.

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

예를 "목록을 반환하는 것"과 비교하면

위의 예는 추가하고 리턴하는리스트를 작성하는 것으로 간주 할 수 있습니다.

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

하나의 큰 차이점이 있습니다. 마지막 절을 참조하십시오.

발전기 사용 방법

iterable은 목록 이해력의 마지막 부분이며 모든 생성자는 반복 가능하므로 자주 사용됩니다.

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

생성기에 대한 더 나은 느낌을 얻으려면 itertools 모듈을 가지고 놀 수 있습니다 (보증 할 때 chain 아닌 chain.from_iterable 을 사용해야합니다). 예를 들어 생성자를 사용하여 itertools.count() 와 같은 무한 길이의 지연 목록을 구현할 수도 있습니다. 당신은 자신의 def enumerate(iterable): zip(count(), iterable) 구현할 수 있습니다 def enumerate(iterable): zip(count(), iterable) 또는 while 루프에서 yield 키워드를 사용합니다.

참고 : 생성자는 실제로 코 루틴 또는 비 결정적 프로그래밍이나 다른 우아한 것들을 구현하는 것과 같이 더 많은 것들에 사용될 수 있습니다. 그러나 내가 여기에 제시하는 "게으른 목록"관점은 당신이 찾을 수있는 가장 보편적 인 사용법입니다.

무대 뒤에서

이것이 "파이썬 반복 프로토콜"의 작동 방식입니다. 즉, list(makeRange(5)) 를 수행 할 때 무슨 일이 벌어지고 있는지입니다. 이것은 이전에 "게으른 점진적 목록"으로 설명했습니다.

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

내장 함수 next() 는 "반복 프로토콜"의 일부이며 모든 반복자에서 발견되는 객체 .next() 함수를 호출합니다. next() 함수 (및 반복 프로토콜의 다른 부분 next() 수동으로 사용하여 일반적으로 가독성을 희생하면서 멋진 작업을 구현할 수 있으므로 그렇게하지 않는 것이 좋습니다.

사소한 점

일반적으로 대부분의 사람들은 다음과 같은 구분에 관심이 없으며 아마도 여기에서 읽지 않으려합니다.

파이썬에서 말하는 iterable 은리스트 [1,2,3] 처럼 "for-loop의 개념을 이해하는"객체이고, 반복자[1,2,3].__iter__() 와 같은 [1,2,3].__iter__() 요청 된 for-loop의 특정 인스턴스이다. [1,2,3].__iter__() . 생성기 는 작성된 방식을 제외하고 모든 반복기와 완전히 동일합니다 (함수 구문 사용).

목록에서 반복기를 요청하면 새로운 iterator가 작성됩니다. 그러나 반복자에서 반복자를 요청하면 (거의하지 않을 것입니다), 그것은 단지 자신의 복사본을 제공합니다.

따라서, 당신이 이런 일을하지 못하는 경우는 드물 것입니다 ...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... 생성자가 반복자 임을 기억하십시오. 즉, 일회용입니다. 재사용하고 싶다면 myRange(...) 다시 호출해야합니다. 결과를 두 번 사용해야하는 경우 결과를 목록으로 변환하고 변수 x = list(myRange(5)) 합니다. 복사 가능한 반복자 Python PEP 표준 제안이 연기 되었기 때문에 절대적으로 필요한 생성자 (예 : 무서운 해킹 메타 프로그래밍을 수행하는 사람)를 절대적으로 필요로하는 사용자는 itertools.tee 를 사용할 수 있습니다.




Here is a mental image of what yield does.

I like to think of a thread as having a stack (even when it's not implemented that way).

When a normal function is called, it puts its local variables on the stack, does some computation, then clears the stack and returns. The values of its local variables are never seen again.

With a yield function, when its code begins to run (ie after the function is called, returning a generator object, whose next() method is then invoked), it similarly puts its local variables onto the stack and computes for a while. But then, when it hits the yield statement, before clearing its part of the stack and returning, it takes a snapshot of its local variables and stores them in the generator object. It also writes down the place where it's currently up to in its code (ie the particular yield statement).

So it's a kind of a frozen function that the generator is hanging onto.

When next() is called subsequently, it retrieves the function's belongings onto the stack and re-animates it. The function continues to compute from where it left off, oblivious to the fact that it had just spent an eternity in cold storage.

Compare the following examples:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

When we call the second function, it behaves very differently to the first. The yield statement might be unreachable, but if it's present anywhere, it changes the nature of what we're dealing with.

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Calling yielderFunction() doesn't run its code, but makes a generator out of the code. (Maybe it's a good idea to name such things with the yielder prefix for readability.)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

The gi_code and gi_frame fields are where the frozen state is stored. Exploring them with dir(..) , we can confirm that our mental model above is credible.




All great answers whereas a bit difficult for newbies.

I assume you have learned return statement.
As an analogy, return and yield are twins.
return means 'Return and Stop' whereas 'yield` means 'Return but Continue'

  1. Try to get a num_list with return .
def num_list(n):
    for i in range(n):
        return i

그것을 실행하십시오 :

In [5]: num_list(3)
Out[5]: 0

See, you get only a single number instead of a list of them,. return never allow you happy to prevail. It implemented once and quit.

  1. There comes yield

Replace return with yield

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990> 

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

Now, you win to get all the numbers.
Comparing to return which runs once and stops, yield runs times you planed.
You can interpret return as return one of them ,
yield as return all of them . This is called iterable .

  1. One more step we can rewrite yield statement with return
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

It's the core about yield .

The difference between a list return outputs and the object yield output is:
You can get [0, 1, 2] from a list object always whereas can only retrieve them from 'the object yield output' once.
So, it has a new name generator object as displayed in Out[11]: <generator object num_list at 0x10327c990> .

In conclusion as a metaphor to grok it,

return and yield are twins,
list and generator are twins.




Many people use return rather than yield but in some cases yield can be more efficient and easier to work with.

Here is an example which yield is definitely best for:

return (in function)

import random

def return_dates():
    dates = [] # with return you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

yield (in function)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # yield makes a generator automatically which works in a similar way, this is much more efficient

Calling functions

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in  dates_generator:
    print(i)

Both functions do the same thing but yield uses 3 lines instead of 5 and has one less variable to worry about.

This is the result from the code:

As you can see both functions do the same thing, the only difference is return_dates() gives a list and yield_dates() gives a generator

A real life example would be something like reading a file line by line or if you just want to make a generator




Yield is an Object

A return in a function will return a single value.

If you want function to return huge set of values use yield .

More importantly, yield is a barrier

like Barrier in Cuda Language, it will not transfer control until it gets completed.

It will run the code in your function from the beginning until it hits yield . Then, it'll return the first value of the loop. Then, every other call will run the loop you have written in the function one more time, returning the next value until there is no value to return.




(My below answer only speaks from the perspective of using Python generator, not the underlying implementation of generator mechanism , which involves some tricks of stack and heap manipulation.)

When yield is used instead of a return in a python function, that function is turned into something special called generator function . That function will return an object of generator type. The yield keyword is a flag to notify the python compiler to treat such function specially. Normal functions will terminate once some value is returned from it. But with the help of the compiler, the generator function can be thought of as resumable. That is, the execution context will be restored and the execution will continue from last run. Until you explicitly call return, which will raise a StopIteration exception (which is also part of the iterator protocol), or reach the end of the function. I found a lot of references about generator but this one from the functional programming perspective is the most digestable.

(Now I want to talk about the rationale behind generator , and the iterator based on my own understanding. I hope this can help you grasp the essential motivation of iterator and generator. Such concept shows up in other languages as well such as C#.)

As I understand, when we want to process a bunch of data, we usually first store the data somewhere and then process it one by one. But this intuitive approach is problematic. If the data volume is huge, it's expensive to store them as a whole beforehand. So instead of storing the data itself directly, why not store some kind of metadata indirectly, ie the logic how the data is computed .

There are 2 approaches to wrap such metadata.

  1. The OO approach, we wrap the metadata as a class . This is the so-called iterator who implements the iterator protocol (ie the __next__() , and __iter__() methods). This is also the commonly seen iterator design pattern .
  2. The functional approach, we wrap the metadata as a function . This is the so-called generator function . But under the hood, the returned generator object still IS-A iterator because it also implements the iterator protocol.

Either way, an iterator is created, ie some object that can give you the data you want. The OO approach may be a bit complex. Anyway, which one to use is up to you.




TL;DR

When you find yourself building a list from scratch...

def squares_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

... yield each piece instead

def squares_the_yield_way(n):
    for x in range(n):
        y = x * x
        yield y                           # with this

This was my first "aha" moment with yield.

yield is a sugary way to say

build a series of stuff

Same behavior:

>>> for square in squares_list(4):
...     print(square)
...
0
1
4
9
>>> for square in squares_the_yield_way(4):
...     print(square)
...
0
1
4
9

Different behavior:

Yield is single-pass : you can only iterate through once. When a function has a yield in it we call it a generator function . And an iterator is what it returns. That's revealing. We lose the convenience of a container, but gain the power of an arbitrarily long series.

Yield is lazy , it puts off computation. A function with a yield in it doesn't actually execute at all when you call it. The iterator object it returns uses magic to maintain the function's internal context. Each time you call next() on the iterator (this happens in a for-loop) execution inches forward to the next yield. ( return raises StopIteration and ends the series.)

Yield is versatile . It can do infinite loops:

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

If you need multiple passes and the series isn't too long, just call list() on it:

>>> list(squares_the_yield_way(4))
[0, 1, 4, 9]

Brilliant choice of the word yield because both meanings apply:

yield — produce or provide (as in agriculture)

...provide the next data in the series.

yield — give way or relinquish (as in political power)

...relinquish CPU execution until the iterator advances.




I was going to post "read page 19 of Beazley's 'Python: Essential Reference' for a quick description of generators", but so many others have posted good descriptions already.

Also, note that yield can be used in coroutines as the dual of their use in generator functions. Although it isn't the same use as your code snippet, (yield) can be used as an expression in a function. When a caller sends a value to the method using the send() method, then the coroutine will execute until the next (yield) statement is encountered.

Generators and coroutines are a cool way to set up data-flow type applications. I thought it would be worthwhile knowing about the other use of the yield statement in functions.




Yet another TL;DR

iterator on list : next() returns the next element of the list

iterator generator : next() will compute the next element on the fly (execute code)

You can see the yield/generator as a way to manually run the control flow from outside (like continue loop 1 step), by calling next, however complex the flow.

NOTE: the generator is NOT a normal function, it remembers previous state like local variables (stack), see other answers or articles for detailed explanation, the generator can only be iterated on once . You could do without yield but it would not be as nice, so it can be considered 'very nice' language sugar.




yield is just like return - it returns whatever you tell it to. The only difference is that the next time you call the function, execution starts from the last call to the yield statement.

In the case of your code, the function get_child_candidates is acting like an iterator so that when you extend your list, it adds one element at a time to the new list.

list.extend calls an iterator until it's exhausted. In the case of the code sample you posted, it would be much clearer to just return a tuple and append that to the list.




While a lot of answers show why you'd use a yield to create a generator, there are more uses for yield . It's quite easy to make a coroutine, which enables the passing of information between two blocks of code. I won't repeat any of the fine examples that have already been given about using yield to create a generator.

To help understand what a yield does in the following code, you can use your finger to trace the cycle through any code that has a yield . Every time your finger hits the yield , you have to wait for a next or a send to be entered. When a next is called, you trace through the code until you hit the yield … the code on the right of the yield is evaluated and returned to the caller… then you wait. When next is called again, you perform another loop through the code. However, you'll note that in a coroutine, yield can also be used with a send … which will send a value from the caller into the yielding function. If a send is given, then yield receives the value sent, and spits it out the left hand side… then the trace through the code progresses until you hit the yield again (returning the value at the end, as if next was called).

예 :

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()



It's returning a generator. I'm not particularly familiar with Python, but I believe it's the same kind of thing as C#'s iterator blocks if you're familiar with those.

There's an IBM article which explains it reasonably well (for Python) as far as I can see.

The key idea is that the compiler/interpreter/whatever does some trickery so that as far as the caller is concerned, they can keep calling next() and it will keep returning values - as if the generator method was paused . Now obviously you can't really "pause" a method, so the compiler builds a state machine for you to remember where you currently are and what the local variables etc look like. This is much easier than writing an iterator yourself.




From a programming viewpoint, the iterators are implemented as thunks

http://en.wikipedia.org/wiki/Thunk_(functional_programming)

To implement iterators/generators/thread pools for concurrent execution/etc as thunks (also called anonymous functions), one uses messages sent to a closure object, which has a dispatcher, and the dispatcher answers to "messages".

http://en.wikipedia.org/wiki/Message_passing

" next " is a message sent to a closure, created by " iter " call.

There are lots of ways to implement this computation. I used mutation but it is easy to do it without mutation, by returning the current value and the next yielder.

Here is a demonstration which uses the structure of R6RS but the semantics is absolutely identical as in python, it's the same model of computation, only a change in syntax is required to rewrite it in python.

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
-> 



For those who prefer a minimal working example, meditate on this interactive Python session:

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed



Links