TIL20. Python : Coroutine ์ด๋ž€?

ID์งฑ์žฌยท2021๋…„ 9์›” 30์ผ
0

Python

๋ชฉ๋ก ๋ณด๊ธฐ
28/39
post-thumbnail

๐Ÿ“Œ ์ด ํฌ์ŠคํŒ…์—์„œ๋Š” python์˜ coroutine์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.



๐ŸŒˆ Coroutine ์ด๋ž€?

๐Ÿ”ฅ Coroutine ์ด๋ž€?

๐Ÿ”ฅ Coroutine ์ƒํƒœ๋ณด๊ธฐ

๐Ÿ”ฅ Coroutine ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํŒจํ„ด

๐Ÿ”ฅ Coroutine์—์„œ return ์‚ฌ์šฉํ•˜๊ธฐ

๐Ÿ”ฅ Coroutine ์ค‘์ฒฉ๊ณผ yield from



1. Coroutine ์ด๋ž€?

๐Ÿค” Coroutine์€ ๋ฌด์—‡์ผ๊นŒ?

โœ”๏ธ ์ฝ”๋ฃจํ‹ด์„ ์ดํ•ดํ•˜๊ธฐ ์ „์— ๋ฉ”์ธ๋ฃจํ‹ด๊ณผ ์„œ๋ธŒ๋ฃจํ‹ด๋ผ๋Š” ๊ฐœ๋…๋ถ€ํ„ฐ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋ฉ”์ธ๋ฃจํ‹ด์€ ์œ„์—์„œ ์•„๋ž˜๋กœ ์ˆœ์ฐจ์ ์œผ๋กœ ์ง„ํ–‰๋˜๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ์˜๋ฏธํ•˜๊ณ , ์„œ๋ธŒ๋ฃจํ‹ด์€ ๋ฉ”์ธ๋ฃจํ‹ด์—์„œ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋‚˜๋ฉด ํ•จ์ˆ˜ ์„ ์–ธ ์œ„์น˜๋กœ ์ด๋™ํ–ˆ๋‹ค๊ฐ€ return์— ์˜ํ•ด ๋‹ค์‹œ ํ•จ์ˆ˜ ํ˜ธ์ถœ ๋ถ€๋ถ„์œผ๋กœ ๋Œ์•„์™€ ๋‹ค์‹œ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ํ๋ฆ„์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
โœ”๏ธ ์˜ˆ๋ฅผ๋“ค์–ด, ์œ„์—์„œ ์•„๋ž˜๋กœ ์ฝ”๋“œ๋ฅผ ์ฝ์–ด๋“ค์ด๋Š” ๋ฉ”์ธ๋ฃจํ‹ด์œผ๋กœ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์ง„ํ–‰๋˜๋‹ค๊ฐ€ ์„œ๋ธŒ๋ฃจํ‹ด์ธ ํ•จ์ˆ˜ ํ˜ธ์ถœ์„ ๋งŒ๋‚˜๋ฉด, ๊ทธ ์œ„์น˜๋ฅผ ๊ธฐ์–ตํ•œ ์ƒํƒœ์—์„œ ํ•จ์ˆ˜ ์„ ์–ธ ์œ„์น˜๋กœ ์ด๋™ํ•ด ์ฝ”๋“œ๋ฅผ ์ฝ์–ด๋“ค์ด๊ณ  return์„ ๋งŒ๋‚˜๋ฉด ๊ฐ’์„ ๊ฐ€์ง„ ์ฑ„๋กœ ๊ธฐ์–ตํ•ด ๋‘” ํ•จ์ˆ˜ํ˜ธ์ถœ ์œ„์น˜๋กœ ๋Œ์•„์˜ค๋Š” ๊ฒƒ์ด ์„œ๋ธŒ๋ฃจํ‹ด์ž…๋‹ˆ๋‹ค.
โœ”๏ธ next๋ฅผ ํ†ตํ•ด ์ œ๋„ˆ๋ ˆ์ดํ„ฐ๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ, yield๋ฅผ ๋งŒ๋‚˜๋ฉด ๊ทธ ์œ„์น˜๋ฅผ ๊ธฐ์–ตํ•˜๊ณ  ๋ฉˆ์ท„๋‹ค๊ฐ€ ๋‹ค์‹œ next๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋‹ค์Œ yield๋ฅผ ๋งŒ๋‚˜๊ธฐ ์ „๊นŒ์ง€ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ œ๋„ˆ๋ ˆ์ดํ„ฐ์˜ ํ•ต์‹ฌ์ด๊ณ , yield๋Š” ๋ฉ”์ธ๋ฃจํ‹ด๊ณผ ์„œ๋ธŒ๋ฃจํ‹ด์ด ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ธŒ๋ฃจํ‹ด์„ ๋™์‹œ์— ์—ฌ๋Ÿฌ๊ฐœ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒํ•ด์ค๋‹ˆ๋‹ค. ์ด๋ฅผ ์ฝ”๋ฃจํ‹ด์ด๋ผ ํ•ฉ๋‹ˆ๋‹ค.
โœ”๏ธ ์ฆ‰, ์ฝ”๋ฃจํ‹ด์€ ๋ฃจํ‹ด ์‹คํ–‰ ์ค‘ ๋ฉˆ์ถค์ด ๊ฐ€๋Šฅํ•˜๊ณ , ํŠน์ • ์œ„์น˜๋กœ ๋Œ์•„๊ฐ”๋‹ค๊ฐ€ ๋‹ค์‹œ ์›๋ž˜ ์œ„์น˜๋กœ ๋Œ์•„์™€ ํ๋ฆ„์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ฐ€๋Šฅํ•˜๊ฒŒํ•ด์ค๋‹ˆ๋‹ค.
โœ”๏ธ ๋”๋ถˆ์–ด ์ฝ”๋ฃจํ‹ด์€ ํ•˜๋‚˜์˜ ์“ฐ๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์Šค์ผ€์ฅด๋ง ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋งค์šฐ ์ ์Šต๋‹ˆ๋‹ค.

๐Ÿค” Coroutine์˜ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ 

โœ”๏ธ 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 << ์ œ๋„ˆ๋ ˆ์ดํ„ฐ๋ฅผ ์‹คํ–‰ ํ›„ ๊ฐ’์„ ๋ณด๋‚ด๋ผ    


2. Coroutine ์ƒํƒœ๋ณด๊ธฐ

๐Ÿค” Coroutine์˜ ์ƒํƒœ ์ข…๋ฅ˜

โœ”๏ธ coroutine ์ƒํƒœ ์ฝ”๋“œ

  • GEN_CREATED : ์ฒ˜์Œ ๋Œ€๊ธฐ ์ƒํƒœ(์ œ๋„ˆ๋ ˆ์ดํ„ฐ ์‹œ์ž‘ ์ „)
  • GEN_RUNNING : ์‹คํ–‰ ์ƒํƒœ
  • GEN_SUSPENDED : yield ๋Œ€๊ธฐ ์ƒํƒœ
  • GEN_CLOSED : ์‹คํ–‰ ์™„๋ฃŒ

๐Ÿค” getgeneratorstate

โœ”๏ธ 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
"""


3. Coroutine ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ํŒจํ„ด

๐Ÿค” next ๋งค์„œ๋“œ ์—†์ด Courtine ์‚ฌ์šฉ

โœ”๏ธ 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


4. Coroutine์—์„œ return ์‚ฌ์šฉํ•˜๊ธฐ

๐Ÿค” ์˜ˆ์™ธ๋ฅผ ์žก์•„๋‚ด์„œ value๊ฐ’ ํ™•์ธ

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


5. Coroutine ์ค‘์ฒฉ๊ณผ yield from

๐Ÿค” coroutine์„ ์ค‘์ฒฉ์‹œ์ผœ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

โœ”๏ธ 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]

๐Ÿค” yield form์€ coroutine์˜ ์ค‘์ฒฉ์„ ์ƒ๋žตํ•œ๊ฒƒ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

โœ”๏ธ 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]
profile
Keep Going, Keep Coding!

0๊ฐœ์˜ ๋Œ“๊ธ€