yield 표현식은 generator function 을 정의할에만 사용되고, 함수의 body 에서만 사용 가능하다. yield 표현식을 사용하기만 하면 일반 함수가 아닌 generator function 이 된다.

generator function 이 호출되면, generator 라고 하는 iterator 를 리턴한다.

이 generator 를 이용해서 generator function 의 실행을 제어할 수 있다. generator 의 메소드들 중 하나가 호출되면 실행이 시작되고, 첫번째 yield 표현식이 실행되면 실행이 연기되고, expression_list 를 generator 의 호출자에게 리턴한다. 실행이 연기되는 것은 모든 local state (지역변수, instruction pointer, internal evaluation stack 등) 들이 보관됨을 의미한다. generator 의 메소드들 중 하나가 호출되면, generator function 은 yield 표현식이 다른 외부 함수를 호출한것과 완전히 동일하게 재개된다. yield 표현식이 재개된 이후의 value 는 실행을 재개시킨 method 에 의존적이다.

이 모든 것들은 generator function 을 coroutine 과 매우 비슷하게 만들어준다; 여러번 yield 하고, 하나이상의 entry point 를 가지고, 실행이 연기될 수 있다. 단 하나 다른점은 generator function 은 yield 이후에 어디에서 실행을 계속 할지에 대해서 제어할 수 없다는 것이다; 제어권은 항상 generator 의 호출자에게 이관된다.

New in version 2.5.

The yield expression is only used when defining a generator function, and can only be used in the body of a function definition. Using a yield expression in a function definition is sufficient to cause that definition to create a generator function instead of a normal function.

When a generator function is called, it returns an iterator known as a generator. That generator then controls the execution of a generator function. The execution starts when one of the generator’s methods is called. At that time, the execution proceeds to the first yield expression, where it is suspended again, returning the value of expression_list to generator’s caller. By suspended we mean that all local state is retained, including the current bindings of local variables, the instruction pointer, and the internal evaluation stack. When the execution is resumed by calling one of the generator’s methods, the function can proceed exactly as if the yield expression was just another external call. The value of the yield expression after resuming depends on the method which resumed the execution.

All of this makes generator functions quite similar to coroutines; they yield multiple times, they have more than one entry point and their execution can be suspended. The only difference is that a generator function cannot control where should the execution continue after it yields; the control is always transferred to the generator’s caller.

Generator-iterator methods

generator.next()

generator function 의 실행을 시작하거나, 마지막으로 실행된 yield 표현식 다음부터 실행을 재개한다. `generator.next()` 가 호출되어 실행이 재개되면 현재의 yield 표현식은 항상 None 이 된다. 다음 yield 표현식까지 실행되고, **expression_list** 는 `next()` 의 호출자에게 반환된다. 만일 generator 가 더 이상의 yield 없이 종료되면 *StopIteration* 예외가 발생한다. 

Starts the execution of a generator function or resumes it at the last executed yield expression. When a generator function is resumed with a next() method, the current yield expression always evaluates to None. The execution then continues to the next yield expression, where the generator is suspended again, and the value of the expression_list is returned to next()‘s caller. If the generator exits without yielding another value, a StopIteration exception is raised.

generator.send(value)

실행을 재개하고, *value* 를 generator function 에게 전달한다. value 아규먼트는 현재의 yield expression 의 결과값이 된다. `send()` 메소드는 generator 의 다음 yield 의 값을 리턴하거나, 다음 yield 가 없는 경우 *StopIteration* 예외를 발생시킨다. `send()` 가 generator 를 시작시키기 위해서 사용된경우, generator 에는 값을 받아들일 yield expression 이 없으므로 반드시 *None* 아규먼트를 사용해야 한다. 

Resumes the execution and “sends” a value into the generator function. The value argument becomes the result of the current yield expression. The send() method returns the next value yielded by the generator, or raises StopIteration if the generator exits without yielding another value. When send() is called to start the generator, it must be called with None as the argument, because there is no yield expression that could receive the value

generator.throw(type, [value, [, traceback]])

generator 의 실행이 멈추어진 곳에 (generator function 의) *type* 의 예외를 던지고, generator function 에 의해서 yield 된 다음 값을 리턴한다. 만일 다른 값을 yield 하지 않고, 종료되면 *StopIteration* 예외가 발생한다. 만일 generator function 이 passed-in 된 예외(`generator.throw()` 로 passwd-in 된)를 처리하지 않거나 다른 예외를 던지는 경우 예외는 호출자에게로 전파된다.

Raises an exception of type type at the point where generator was paused, and returns the next value yielded by the generator function. If the generator exits without yielding another value, a StopIteration exception is raised. If the generator function does not catch the passed-in exception, or raises a different exception, then that exception propagates to the caller.

generator.close()

generator function 의 실행이 멈추어진 곳에 *GeneratorExit* 예외를 발생시킨다. generator function 은 *StopIteration* 예외(정상적으로 종료되거나, 이미 종료된 경우), *GenerateExit* (발생시킨 예외를 잡지 않음으로써) 예외를 발생시키거나, 호출자에게 리턴한다. 만일 generator 가 값을 yield 하고 있는 경우 *RuntimeError* 가 발생한다. 만일 다른 예외를 던지는 경우 호출자에게로 전파된다. `close()` 는 generator 가 예외나 일반적인 종료에 의해서 이미 종료된 경우 아무짓도 하지 않는다. 

Raises a GeneratorExit at the point where the generator function was paused. If the generator function then raises StopIteration (by exiting normally, or due to already being closed) or GeneratorExit (by not catching the exception), close returns to its caller. If the generator yields a value, a RuntimeError is raised. If the generator raises any other exception, it is propagated to the caller. close() does nothing if the generator has already exited due to an exception or normal exit.


In [7]:
def echo(value=None):
    print "Execution starts when 'next()' is called for the first time."
    try:
        while True:
            try:
                value = (yield value)
            except Exception, e:
                value = e
    finally:
        print "Don't forget to clean up when 'close()' is called."
generator = echo(1)
print generator.next()
print generator.next()
print generator.send(2)
generator.throw(TypeError, "spam")
generator.close()


Execution starts when 'next()' is called for the first time.
1
None
2
Don't forget to clean up when 'close()' is called.

2. Coroutine


In [36]:
def grep(pattern):
    """
    simple coroutine sample  (yield 예제코드와 다를바 없는데... ??)
    """
    try:
        print 'looking for {0}'.format(pattern)
        while True:
            line = (yield)               # yield 를 입력으로 사용, consumer == coroutine, 
            if pattern in line:
                print '\'{0}\' was found in line, \'{1}\''.format(pattern, line)
    finally:
        print 'coroutine terminated....'

        
crtn = grep("python")
crtn.next()                                                       # start iterator, and stop first `yield` expression

crtn.send("python coroutine is not easy to understand to me.")    # suspended at `yield` expression and wait for callers input.
crtn.send("no p-y-thon word")                                     # print nothing.

crtn.close()


looking for python
'python' was found in line, 'python coroutine is not easy to understand to me.'
coroutine terminated....

In [41]:
def coroutine_decorator(generator_function):
    """
    데코레이터를 통해서 `def start()` 를 호출하게 만듦으로써, `gc.next()` 를 자동으로 호출하게 한다.     
    """    
    def start(*args, **kwargs):
        cr = generator_function(*args, **kwargs)  # generator function 을 호출해서 generator 를 만들고,
        cr.next()                                 # generator.next() 를 호출해서 generator function 을 시작, 
                                                  # generator function 내의 `yield` expression 까지 실행한다. 
        return cr                                 # generator 를 리턴한다. 
    return start

@coroutine_decorator
def grep(pattern):
    """
    simple coroutine sample  (yield 예제코드와 다를바 없는데... ??)
    """
    try:
        print 'looking for {0}'.format(pattern)
        while True:
            line = (yield)               # yield 를 입력으로 사용, consumer == coroutine, 
            if pattern in line:
                print '\'{0}\' was found in line, \'{1}\''.format(pattern, line)
    finally:
        print 'coroutine terminated....'

        
g = grep("python")
# Notice how you don't need a next() call here
g.send("Yeah, but no, but yeah, but no")
g.send("A series of tubes")
g.send("python generators rock!")


looking for python
coroutine terminated....
'python' was found in line, 'python generators rock!'