๐ ์ด ํฌ์คํ ์์๋ python์ coroutine์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค.
๐ฅ Coroutine ์ด๋?
๐ฅ Coroutine ์ํ๋ณด๊ธฐ
๐ฅ Coroutine ๋ฐ์ฝ๋ ์ดํฐ ํจํด
๐ฅ Coroutine์์ return ์ฌ์ฉํ๊ธฐ
๐ฅ Coroutine ์ค์ฒฉ๊ณผ yield from
โ๏ธ ์ฝ๋ฃจํด์ ์ดํดํ๊ธฐ ์ ์ ๋ฉ์ธ๋ฃจํด๊ณผ ์๋ธ๋ฃจํด๋ผ๋ ๊ฐ๋
๋ถํฐ ์์๋ณด๊ฒ ์ต๋๋ค. ๋ฉ์ธ๋ฃจํด์ ์์์ ์๋๋ก ์์ฐจ์ ์ผ๋ก ์งํ๋๋ ํ๋ก์ธ์ค๋ฅผ ์๋ฏธํ๊ณ , ์๋ธ๋ฃจํด์ ๋ฉ์ธ๋ฃจํด์์ ํจ์๋ฅผ ๋ง๋๋ฉด ํจ์ ์ ์ธ ์์น๋ก ์ด๋ํ๋ค๊ฐ return์ ์ํด ๋ค์ ํจ์ ํธ์ถ ๋ถ๋ถ์ผ๋ก ๋์์ ๋ค์ ํ๋ก์ธ์ค๋ฅผ ์งํํ๋ ํ๋ฆ์ ์๋ฏธํฉ๋๋ค.
โ๏ธ ์๋ฅผ๋ค์ด, ์์์ ์๋๋ก ์ฝ๋๋ฅผ ์ฝ์ด๋ค์ด๋ ๋ฉ์ธ๋ฃจํด์ผ๋ก ํ๋ก์ธ์ค๊ฐ ์งํ๋๋ค๊ฐ ์๋ธ๋ฃจํด์ธ ํจ์ ํธ์ถ์ ๋ง๋๋ฉด, ๊ทธ ์์น๋ฅผ ๊ธฐ์ตํ ์ํ์์ ํจ์ ์ ์ธ ์์น๋ก ์ด๋ํด ์ฝ๋๋ฅผ ์ฝ์ด๋ค์ด๊ณ return์ ๋ง๋๋ฉด ๊ฐ์ ๊ฐ์ง ์ฑ๋ก ๊ธฐ์ตํด ๋ ํจ์ํธ์ถ ์์น๋ก ๋์์ค๋ ๊ฒ์ด ์๋ธ๋ฃจํด์
๋๋ค.
โ๏ธ next๋ฅผ ํตํด ์ ๋๋ ์ดํฐ๋ฅผ ์คํํ์ ๋, yield๋ฅผ ๋ง๋๋ฉด ๊ทธ ์์น๋ฅผ ๊ธฐ์ตํ๊ณ ๋ฉ์ท๋ค๊ฐ ๋ค์ next๋ฅผ ์คํํ๋ฉด ๋ค์ yield๋ฅผ ๋ง๋๊ธฐ ์ ๊น์ง ์คํํ๋ ๊ฒ์ด ์ ๋๋ ์ดํฐ์ ํต์ฌ์ด๊ณ , yield๋ ๋ฉ์ธ๋ฃจํด๊ณผ ์๋ธ๋ฃจํด์ด ์๋ฐฉํฅ ํต์ ์ ๊ฐ๋ฅํ๊ฒ ํ๊ธฐ ๋๋ฌธ์ ์๋ธ๋ฃจํด์ ๋์์ ์ฌ๋ฌ๊ฐ๋ฅผ ์คํํ ์ ์๊ฒํด์ค๋๋ค. ์ด๋ฅผ ์ฝ๋ฃจํด์ด๋ผ ํฉ๋๋ค.
โ๏ธ ์ฆ, ์ฝ๋ฃจํด์ ๋ฃจํด ์คํ ์ค ๋ฉ์ถค์ด ๊ฐ๋ฅํ๊ณ , ํน์ ์์น๋ก ๋์๊ฐ๋ค๊ฐ ๋ค์ ์๋ ์์น๋ก ๋์์ ํ๋ฆ์ ์ํํ๊ธฐ ๋๋ฌธ์ ๋์์ฑ ํ๋ก๊ทธ๋๋ฐ ๊ฐ๋ฅํ๊ฒํด์ค๋๋ค.
โ๏ธ ๋๋ถ์ด ์ฝ๋ฃจํด์ ํ๋์ ์ฐ๋ ๋๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ค์ผ์ฅด๋ง ์ค๋ฒํค๋๊ฐ ๋งค์ฐ ์ ์ต๋๋ค.
โ๏ธ yield์ ์ค๋ฅธ์ชฝ์ next ํจ์๋ฅผ ํตํด ์ ๋๋ ์ดํฐ๊ฐ ์คํ๋ฌ์ ๋ ๋ฉ์ธ๋ฃจํด์ผ๋ก ๋ฐํํ๋ ๊ฐ์ด๊ณ , yield ์ผ์ชฝ์ ํ ๋น๋ ๊ฐ์ ๋ฉ์ธ๋ฃจํด์์ ์ ๋๋ ์ดํฐ ๋ด๋ถ๋ก ๊ฐ์ ์ ๋ฌํ๊ธฐ ์ํ ๋ณ์์
๋๋ค.
โ๏ธ ์ฆ, yield๋ฅผ ๋ง๋ ์ค์ง๋์๋ค๊ฐ ๋ฉ์ธ๋ฃจํธ์์ send๋ฅผ ํตํด yield์ ๊ฐ์ ์ ๋ฌํ๋ฉด ๋ค์ yield๊ฐ ์คํ๋๊ณ send์ ๊ฐ์ ํจ์ ๋ด๋ถ๋ก ์ ๋ฌ๋ฉ๋๋ค.
โ๏ธ coroutine์ ์ด์ฒ๋ผ yield๋ฅผ ํตํด ์๋ฐฉํฅ์ผ๋ก ๋์์ฑ์ผ๋ก ํ๋ฆ์ ์ด๋ฅผ ๊ฐ๋ฅํ๊ฒ ํด์ค๋๋ค.
def coroutine1(): print("coroutine started.") i = yield # ๐ yield๋ฅผ ๋ง๋๋ฉด ์์น๋ฅผ ๊ธฐ์ตํ๊ณ ๋ฉ์ถค print("coroutine received : {}".format(i)) c1 = coroutine1() print(c1) # ๐ <generator object coroutine1 at 0x7fa9a4301040> print(type(c1)) # ๐ <class 'generator'> next(c1) # ๐ coroutine started. <<< "yield ์ ๊น์ง ์คํํ๊ณ , i=yield์ ์์น์์ stop" c1.send(100) # coroutine received : 100 <<< ๊ฐ์ send ๋งค์๋๋ก ์ ๋ฌํ๋ฉด i์ 100์ด ๋ด๊ฒจ ์คํ
โ๏ธ send ๋งค์๋๋ก ๊ฐ์ ์ ๋ฌํ์ง ์๊ณ , next๋ฅผ ๋ค์ ์คํํ๋ฉด ๋ฉ์ธ๋ฃจํด์์๋ default๋ก None์ ์ ๋ฌํฉ๋๋ค.
def coroutine1(): print("coroutine started.") i = yield print("coroutine received : {}".format(i)) c1 = coroutine1() next(c1) # ๐ coroutine started. next(c1) """ coroutine received : None StopIteration """
โ๏ธ next๋ฅผ ํตํด ์ ๋๋ ์ดํฐ๋ฅผ ์คํํ๊ธฐ ์ send๋ก yield์ ๊ฐ์ ์ ๋ฌํ๋ฉด TypeError๊ฐ ๋ฐ์ํฉ๋๋ค.
โ๏ธ ์ ๋๋ ์ดํฐ๊ฐ ์์๋๊ธฐ ์ ์ ๋ฉ์ธ๋ฃจํธ์์ yield ์ผ์ชฝ์ ๊ฐ์ ์ ๋ฌํ๊ธฐ ๋๋ฌธ์
๋๋ค. ์ฆ, yield์์ ๋ฉ์ถฐ ๊ฐ์ ์ ๋ฌ๋ฐ์ ์ ์๋๋ก ์ค๋น๋ ์ํ์์ send๋ฅผ ํตํด ์ ๋ฌํด์ผํฉ๋๋ค.
def coroutine1(): print("coroutine started.") i = yield # ๐ yield๋ฅผ ๋ง๋๋ฉด ์์น๋ฅผ ๊ธฐ์ตํ๊ณ ๋ฉ์ถค print("coroutine received : {}".format(i)) c2 = coroutine1() c2.send(100) # ๐ TypeError: can't send non-None value to a just-started generator << ์ ๋๋ ์ดํฐ๋ฅผ ์คํ ํ ๊ฐ์ ๋ณด๋ด๋ผ
โ๏ธ coroutine ์ํ ์ฝ๋
โ๏ธ getgeneratorstate๋ inspect์์ importํ์ฌ ์ฌ์ฉํฉ๋๋ค.
โ๏ธ yield ์ผ์ชฝ์ ๋ฉ์ธ๋ฃจํธ์์ send๋ก ์ ๋ฌ๋๋ ๊ฐ์ ๋ฐ๋ ๊ณณ์ด๊ณ , yield ์ค๋ฅธ์ชฝ์ ํ๋ฆ์ด ๋ค์ ์์๋ ๋, ๋ฉ์ธ๋ฃจํด์ผ๋ก return๋๋ ๊ฐ์
๋๋ค.
from inspect import getgeneratorstate # ๐ "getgeneratorstate" import def coroutine2(x): print("coroutine started. : {}".format(x)) y = yield x print("coroutine received. : {}".format(y)) z = yield x + y print("coroutine received. : {}".format(z)) c3 = coroutine2(10) print(getgeneratorstate(c3)) # GEN_CREATED print(next(c3)) """ coroutine started. : 10 ๐ ํจ์์์์ ์ถ๋ ฅํ ๊ฐ 10 ๐ next(c3)์ ๊ฐ == yield์์ ๋ฉ์ธ๋ฃจํด์ผ๋ก ๋ฐํํ ๊ฐ """ print(getgeneratorstate(c3)) # GEN_SUSPENDED print(c3.send(15)) """ coroutine received. : 15 ๐ ํจ์์์์ ์ถ๋ ฅํ ๊ฐ 25 ๐ next(c3)์ ๊ฐ == yield์์ ๋ฉ์ธ๋ฃจํด(x+y)์ผ๋ก ๋ฐํํ ๊ฐ """ print(c3.send(20)) """ coroutine received. : 20 ๐ ํจ์์์์ ์ถ๋ ฅํ ๊ฐ StopIteration """
โ๏ธ Coroutine๋ฅผ next ๋งค์๋ ์์ด ์ฌ์ฉํ๋ ค๋ฉด, ์๋์ฒ๋ผ ๋ฐ์ฝ๋ ์ดํฐ ํจํด์ ํ์ฉํ ์ ์์ต๋๋ค.
from functools import wraps # ๋ฐ์ฝ๋ ์ดํฐ ์์ฑ def coroutine(func): """Decorator run until yield""" @wraps(func) def primer(*args, **kwargs): gen = func(*args, **kwargs) next(gen) return gen return primer # Coroutine ์์ฑ @coroutine def sumer(): total = 0 term = 0 while True: term = yield total total += term sexysum = sumer() print(sexysum.send(100)) # 100 print(sexysum.send(40)) # 140 print(sexysum.send(60)) # 200
def averager_return(): total = 0.0 cnt = 0 avg = None while True: # ๐ ๋ฌดํ๋ฐ๋ณต term = yield if term is None: break # ๐ send๊ฐ์ด None์ด๋ฉด break total += term cnt += 1 avg = total / cnt return 'Average : {}'.format(avg) avger = averager_return() next(avger) avger.send(10) avger.send(30) avger.send(50) try: avger.send(None) # ๐ send๊ฐ์ผ๋ก None ์ ๋ฌ except StopIteration as e: # ๐ ์๋ฌ๊ฐ์ก์์ value ํ์ธ print(e.value) # Average : 30.0
โ๏ธ for๋ฌธ์ผ๋ก coroutine์ ์ค์ฒฉ์ํค๋ฉด, next๋ฅผ ์คํ๋ ๋๋ง๋ค yield์ ์ค๋ฅธ์ชฝ๊ฐ์ ๋ฐํํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
โ๏ธ ๋ํ coroutine ๊ตฌ๋ฌธ์ list๋ก ์ถ๋ ฅํ๋ฉด ํ๋ฒ์ ๋ชจ๋ ๊ฐ์ด ๋์ต๋๋ค.
def gen1(): for x in 'AB': yield x for y in range(1,4): yield y test1 = gen1() print(next(test1)) # A print(next(test1)) # B print(next(test1)) # 1 print(next(test1)) # 2 print(next(test1)) # 3 # print(next(test1)) # StopIteration test2 = gen1() print(list(test2)) # ['A', 'B', 1, 2, 3]
โ๏ธ python 3.7 ๋ฒ์ ๋ถํฐ๋ yeild from์ด await์ผ๋ก ๋ฐ๋์๋ค๊ณ ํฉ๋๋ค.
def gen2(): yield from 'AB' yield from range(1,4) test3 = gen2() print(next(test3)) # A print(next(test3)) # B print(next(test3)) # 1 print(next(test3)) # 2 print(next(test3)) # 3 # print(next(test3)) # StopIteration print(list(gen2())) # ['A', 'B', 1, 2, 3]