파이썬 코루틴(coroutine)

Yeonu·2020년 11월 27일
0

Python 이론

목록 보기
26/30
post-thumbnail

👉 코루틴

코루틴(coroutine)은 cooperative routine를 의미하는데 서로 협력하는 루틴이라는 뜻이다. 즉, 메인 루틴과 서브 루틴처럼 종속된 관계가 아니라 서로 대등한 관계이며 특정 시점에 상대방의 코드를 실행한다.

주로 파일처리, 네트워크 처리에 활용된다.



👉 코루틴에 값 보내기

코루틴은 제너레이터의 특별한 형태다. 제너레이터는 yield로 값을 발생시켰지만 코루틴은 yield로 값을 받아올 수 있다.

  1. 코루틴에 값을 보내면서 코드를 실행할 때는 send 메서드를 사용한다.
  2. 그리고 send 메서드가 보낸 값을 받아오려면 (yield) 형식으로 yield를 괄호로 묶어준 뒤 변수에 저장한다.

코루틴객체.send(값)
변수 = (yield)

def number_coroutine():
    while True:        # 코루틴을 계속 유지하기 위해 무한 루프 사용
        x = (yield)    # 코루틴 바깥에서 값을 받아옴, yield를 괄호로 묶어야 함
        print(x)

co = number_coroutine()
next(co)
# co = number_coroutine()과 같이 코루틴 객체를 생성한 뒤
# next(co)로 코루틴 안의 코드를 최초로 실행하여 yield까지 코드를 실행(co.__next__()를 호출해도 상관없다.)

next(코루틴객체)


co.send(1)    # 코루틴에 숫자 1을 보냄
co.send(2)    # 코루틴에 숫자 2을 보냄
co.send(3)    # 코루틴에 숫자 3을 보냄

# 실행 결과
1
2
3

일반 함수를 호출하면 코드를 한 번만 실행할 수 있지만, 코루틴은 코드를 여러 번 실행할 수 있다. 함수의 코드를 실행하는 지점을 진입점(entry point)이라고 하는데, 코루틴은 진입점이 여러 개인 함수다.



👉 코루틴 바깥으로 값 전달하기

(yield 변수) 형식으로 yield에 변수를 지정한 뒤 괄호로 묶어주면 값을 받아오면서 바깥으로 값을 전달한다. 그리고 yield를 사용하여 바깥으로 전달한 값은 next 함수(__next__ 메서드)와 send 메서드의 반환값으로 나온다.

변수 = (yield 변수)
변수 = next(코루틴객체)
변수 = 코루틴객체.send(값)

def sum_coroutine():
    total = 0
    while True:
        x = (yield total)   # 코루틴 바깥에서 값을 받아오면서 바깥으로 값을 전달
        total += x

co = sum_coroutine()
print(next(co))  # 0: 코루틴 안의 yield까지 코드를 실행하고 코루틴에서 나온 값 출력

print(co.send(1))    # 1: 코루틴에 숫자 1을 보내고 코루틴에서 나온 값 출력
print(co.send(2))    # 3: 코루틴에 숫자 2를 보내고 코루틴에서 나온 값 출력
print(co.send(3))    # 6: 코루틴에 숫자 3을 보내고 코루틴에서 나온 값 출력

# 실행 결과
0
1
3
6

next는 코루틴의 코드를 실행하지만 값을 보내지 않을 때 사용하고, send는 값을 보내면서 코루틴의 코드를 실행할 때 사용한다.


👉 제너레이터와 코루틴의 차이점

제너레이터 : next 함수(__next__ 메서드)를 반복 호출하여 값을 얻어내는 방식
코루틴 : next 함수(__next__ 메서드)를 한 번만 호출한 뒤 send로 값을 주고 받는 방식


값을 보내지 않고 코루틴의 코드 실행하기
값을 보내지 않으면서 코루틴의 코드를 실행할 때는 next 함수(__next__ 메서드)만 사용하면 된다. 생각해보면 이 방식이 일반적인 제너레이터다.



👉 코루틴을 종료하고 예외 처리하기

close()를 사용한다.


👉 GeneratorExit 예외 처리하기

코루틴 객체에서 close 메서드를 호출하면 코루틴이 종료될 때 GeneratorExit 예외가 발생한다. 따라서 이 예외를 처리하면 코루틴의 종료 시점을 알 수 있다.

def number_coroutine():
    try:
        while True:
            x = (yield)
            print(x, end=' ')
    except GeneratorExit:    # 코루틴이 종료 될 때 GeneratorExit 예외 발생
        print()
        print('코루틴 종료')

co = number_coroutine()
next(co)

for i in range(20):
    co.send(i)

co.close()

# 실행 결과
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 
코루틴 종료



👉 코루틴 안에서 예외 발생시키기

코루틴 안에 예외를 발생 시킬 때는 throw 메서드를 사용한다. 이때 throw 메서드에 지정한 에러 메시지는 except as의 변수에 들어간다.

코루틴객체.throw(예외이름, 에러메시지)

다음은 코루틴에 숫자를 보내서 누적하다가 RuntimeError 예외가 발생하면 에러 메시지를 출력하고 누적된 값을 코루틴 바깥으로 전달한다.

def sum_coroutine():
    try:
        total = 0
        while True:
            x = (yield)
            total += x
    except RuntimeError as e:
        print(e)
        yield total    # 코루틴 바깥으로 값 전달

co = sum_coroutine()
next(co)

for i in range(20):
    co.send(i)

print(co.throw(RuntimeError, '예외로 코루틴 끝내기')) # 190
                                    # 코루틴의 except에서 yield로 전달받은 값

# 실행 결과
예외로 코루틴 끝내기
190



👉 하위 코루틴의 반환값 가져오기

yield from에 코루틴를 지정하면 해당 코루틴에서 return으로 반환한 값을 가져온다.
( ⚠ 제너레이터에서 yield from을 사용하면 값을 바깥으로 여러 번 전달한다. 제네레이터 정리)

변수 = yield from 코루틴()

def accumulate():
    total = 0
    while True:
        x = (yield)         # 코루틴 바깥에서 값을 받아옴
        if x is None:       # 받아온 값이 None이면
            return total    # 합계 total을 반환
        total += x

def sum_coroutine():
    while True:
        total = yield from accumulate()    # accumulate의 반환값을 가져옴
        print(total)

co = sum_coroutine()
next(co)

for i in range(1, 11):    # 1부터 10까지 반복
    co.send(i)            # 코루틴 accumulate에 숫자를 보냄
co.send(None)             # 코루틴 accumulate에 None을 보내서 숫자 누적을 끝냄

for i in range(1, 101):   # 1부터 100까지 반복
    co.send(i)            # 코루틴 accumulate에 숫자를 보냄
co.send(None)             # 코루틴 accumulate에 None을 보내서 숫자 누적을 끝냄

# 실행 결과
55
5050



👉 StopIteration 예외 발생시키기

코루틴도 제너레이터이므로 return을 사용하면 StopIteration이 발생한다. raise로 StopIteration 예외를 직접 발생시키고 값을 지정하면 yield from으로 값을 가져올 수 있다.
파이썬 3.7부터는 사용불가. 그냥 return 값 사용

raise StopIteration(값)

def accumulate():
    total = 0
    while True:
        x = (yield)                       # 코루틴 바깥에서 값을 받아옴
        if x is None:                     # 받아온 값이 None이면
            raise StopIteration(total)    # StopIteration에 반환할 값을 지정(파이썬 3.6 이하)
        total += x

def sum_coroutine():
    while True:
        total = yield from accumulate()    # accumulate의 반환값을 가져옴
        print(total)

co = sum_coroutine()
next(co)

for i in range(1, 11):    # 1부터 10까지 반복
    co.send(i)            # 코루틴 accumulate에 숫자를 보냄
co.send(None)             # 코루틴 accumulate에 None을 보내서 숫자 누적을 끝냄

for i in range(1, 101):   # 1부터 100까지 반복
    co.send(i)            # 코루틴 accumulate에 숫자를 보냄
co.send(None)             # 코루틴 accumulate에 None을 보내서 숫자 누적을 끝냄

accumulate에서 return total 대신 raise StopIteration(total)을 사용했습니다. 이때도 yield from은 accumulate의 total을 가져온다.




출처, 강의
💡 42.1 ~
💡 42.4

0개의 댓글