티스토리 뷰

반응형
파이썬 책들을 보면, 코루틴은 파이썬에서 문서가 가장 빈약하고, 모호하고, 분명히 쓸모없는 기능이다. - david beazley 

사실 작성하면서, 제대로 외울 수 없다고 생각한다! 기록용으로 정리해둔다.

주 참고문서 1:
PEP255 Simple Generators
PEP325 Resource-Release Support for Generators
PEP342 Coroutines via Enhanced Generators

참고문서 2: http://www.dabeaz.com/coroutines/

언어 : python3.9.14

코루틴을 이해하기 위해서 가장 먼저 이터레이터에 대해 이야기 해볼 필요가 있다.

이터레이터란?

PEP234 Iterators (30-Jan-2001)
일반적으로, 이터레이터란 반복자 인터페이스를 뜻한다. 다음 값을 가져오는 반복자를 통해 가져올 수 있다. 파이썬에서는 반복자의 진행을 멈추기 위해서 StopIteration을 제공한다.

제너레이터란?

PEP255 Simple Generators (18-May-2001)
제너레이터는 yield 문을 활용한 함수를 뜻한다. 함수를 선언하여 내부를 작성할 때 yield 문을 작성한다면 그것은 무조건 제너레이터 계열의 함수다. (Generator Function/Async Generator Function) 이를, 제너레이터 함수라고 부른다. 편의상 제너레이터 함수를 제너레이터라고 불러도 상관이 없을 듯 하다.

Note that when the intent is clear from context, the unqualified name “generator” may be used to refer either to a generator-function or a generator-iterator. -- pep 255

제너레이터는 이터레이터와 함수 두 개를 동시에 걸치는 개념이다. 그래서 이해하기가 까다로울 수 있다. 먼저 제너레이터와 이터레이터의 반복자로서의 차이는, 이터레이터는 Lazy하지 않지만 제너레이터는 Lazy하게 값을 받을 수 있다는 점이다. 그리고 함수와 제너레이터의 차이는 함수는 한번 실행하면 return 문까지 실행되어 값이 반환된다. 제너레이터 함수는 next()가 불리기 전까지 유용한 값을 반환하지 않는다. 제너레이터 함수를 호출하는 것 자체는 그저 제너레이터 이터레이터를 반환할 뿐이다. 제너레이터 함수는 yield를 만나면 동작을 멈춘다. 지역변수, 인스터럭션 포인터, internal evaluation stack을 저장하고, 다음 next()가 실행되면 이전 멈춘 상태에서 그대로 진행할 수 있다. (Lazy) 제너레이터에서의 return은 함수에서의 return과 동작이 다르다. 함수에서의 return은 값을 반환하는 반면 제너레이터의 return 은 앞서 설명한 StopIteration Exception이 발생한다. 제너레이터가 도입된 초창기 에서는 return 자체가 사용이 안되었다고 한다. 현재의 return이 사용 가능해진 것은 개선이 되서 적용이 되었다.
함수 내에서 yield 문을 사용하게 되면 자동으로 제너레이터 함수가 되기에 굉장히 헷갈린다. 비동기 함수에 async를 붙이는 것과 다르게, 제너레이터로 명시하려면 함수 내부에서 yield 문을 사용하기만 하면 자동으로 제너레이터 함수가 되기 때문이다. 이러한 어려움은 코루틴을 이해하는데 더 어려움이 있다.

코루틴이란?

PEP342 Coroutines via Enhanced Generators (10-May-2005)

def simple_coroutine():
    print('-> coroutine started')
    x = yield 13
    print('-> coroutine received: ', x)
my_coro = simple_coroutine() 
print(next( my_coro ))
> -> coroutine started
> 13 
my_coro.send(42)
> -> coroutine received: 42 

제너레이터와 코루틴은 정말 한 끗 차이이다. yield 문은 이전까지 함수를 제너레이터로 선언하는 statement로 취급되었으나, 코루틴 개념이 적용되면서 expression으로 활용 된다. coroutine을 통하여 함수를 호출하는 호출자 caller와 피호출자(코루틴,제너레이터) callee 간의 커뮤니케이션이 가능하다. 아래의 코드는 caller(호출자)가 중간 코루틴, 하위 코루틴에게 메시지를 보내는 예제이다. 코루틴을 통해 다른 언어에서 제공하는 이벤트 핸들러(C#), 콜백 함수(js) 등의 개념을 구현할 수 있다고 검토된다. 그래서 사용성이 풍부하며 코드자체가 이해하기가 간혹 어려울 수 있다고 생각한다.

from typing import Coroutine, List

coroutines : List[Coroutine] = []
def add(name: str):
    co = listener(name)
    next(co)
    coroutines.append(co)

def eventaggreagtor():
    while True:
        data = yield
        print(f'message queue listened. {data}')
        for co in coroutines:
            co.send(data)

def listener(name):
    while True:
        da = yield
        print(f'I\'m {name}. listened {da}')

def main():
    ea = eventaggreagtor()
    add('flower')
    add('james')
    add('yohan')
    next(ea)
    ea.send("hi!")
    ea.send("bloom")

main()
> message queue listened. hi!
> I'm flower. listened hi!
> I'm james. listened hi!
> I'm yohan. listened hi!
> message queue listened. bloom
> I'm flower. listened bloom
> I'm james. listened bloom
> I'm yohan. listened bloom
def eventaggreagtor():
    while True:
        data = yield
        print(f'message queue listened. {data}')
        for co in coroutines:
            d = co.send(data)
            print(f'{d[0]} listend {d[1]} times')


def listener(name):
    count = 0
    while True:
        da = yield (name, count)
        print(f'I\'m {name}. listened {da}')
        count += 1

> message queue listened. hi!
> I'm flower. listened hi!
> flower listend 1 times
> I'm james. listened hi!
> james listend 1 times
> I'm yohan. listened hi!
> yohan listend 1 times
> message queue listened. bloom
> I'm flower. listened bloom
> flower listend 2 times
> I'm james. listened bloom
> james listend 2 times
> I'm yohan. listened bloom
> yohan listend 2 times

이 외에도, coroutine delegation 을 지원하 코루틴 체인에서 코루틴 위임을 지원하는 'yield from' 키워드가 있다. 추가적으로 찾아보면 좋을 듯 하며 이상 문서를 마칩니다.

감사합니다.

결론 : 마치 covariance, invariance 처럼... 굳이 정확히 알필요없지만 알게되면 더 하이퀄리티의 프로그래밍을 할 수 있는 도구. 또한, 파이썬 언어는 계속해서 진화하기 때문에 spec이 바뀌기도 해서. 언어의 스펙은 언제든지 바뀔수 있고 용어가 바뀔 수 있지만, 필요한 도구라고 생각이 든다.

반응형