파이썬 yield from – 다른 코루틴에게 작업을 위임하기

파이썬에선 함수 내부에 yield 키워드가 쓰였다면 이는 일반적인 함수가 아니라 제너레이터를 만드는 제너레이터 함수(혹은 코루틴 함수)가 된다. 제너레이터는 next() 함수를 통해서 생성하는 값을 꺼낼 수 있고, 동시에 .send() 메소드를 써서 그 내부로 값을 전달할 수 있다. 특히 이렇게 값을 주입해 줄 수 있는 제너레이터를 코루틴이라 한다고 했다.

코루틴 혹은 제너레이터의 내부에서 다른 코루틴이나 제너레이터의 결과값을 그대로 사용하는 경우가 있을 수 있다. 예를 들어 다음의 경우, 주어진 값으로부터 1씩 내렸다가 다시 0부터 n-1까지 값을 생성하는 제너레이터가 있다고 하자.

def foo(n):
  for i in range(n, 0, -1):
    yield i
  for i in range(n):
    yield i

list(foo(5))
# -> [5, 4, 3, 2, 1, 0, 1, 2, 3, 4]

이렇게 제너레이터 내부에서 다른 제너레이터의 결과를 사용하는 경우에 yield from 으로 코드를 간결하게 만들 수 있다.

def foo(n):
  yield from range(n, 0, -1)
  yield from range(n)

사실 yield from은 실용적인 맥락에서는 별로 쓰일일이 없다. Python 3.5에서 비동기 코루틴의 결과를 기다리는 await 문이 추가되기 전에 같은 기능을 위한 문법으로 추가된 것이기 때문이다.


yield from이 작동하는 방식은 간단하다. 제너레이터/코루틴 내부에서 다른 제너레이터/코루틴에 대해서 yield from subcoroutine()을 사용하는 것이다.

  1. 이 경우 해당 코루틴에 대해 next(co), co.send(v)를 호출하는 것은 곧장 subcoroutine()의 동작으로 연결되며, co 내부의 yield from 지점에서는 코드가 진행되지 않는다.
  2. 서브코루틴이 동작을 완료하여 StopIteration 예외를 일으키면 yield from 구문이 종료되고 다음 라인으로 실행 흐름이 이동한다.

yield from을 사용하여 작업을 위임하는 경우, next()send()가 동일하게 위임된다. 차이가 있다면 next()send(None)과 똑같이 동작하게 된다는 것이다.

다음은 해당 기능을 설명한 PEP380에 등장하는 예제이다. 누계를 받아서 리턴해주는 acc() 코루틴을 사용해서 여러 수열의 누적합을 만드는 예이다. 유용할지는 모르지만, 작업 위임이 어떤식으로 동작하는지는 볼 수 있는 예이다.

def acc():
    s = 0
    while True:
        n = yield s
        if n is None:
            return s
        s += n
        

@coroutine
def gather(xs):
    while True:
        subtotal = yield from acc()
        xs.append(subtotal)

ns = []
g = gather(ns)
g.sender(10) # -> 10
g.sender(20) # -> 30
g.sender(30) # -> 60
next(g)
# ns = [60]

파이썬의 반복문과 iterable에 대해

리스트, 튜플, 문자열, 사전의 공통점은? 모두 for ... in 문에 사용할 수 있다는 점이다. 리스트는 for 문을 통해서 개별 원소에 대한 반복 작업을 할 수 있는데, 튜플과 문자열 역시 이와 똑같은 동작을 수행하며 사전의 경우에는 사전 내의 각 키에 대해서 순회하는 기능을 제공한다. 파이썬에서는 이와 같이 for ... in 구문을 통해서 반복이 가능한 타입들을 묶어서 iterable이라고 부르는데, 이는 파이썬의 기본 개념에서 매우 중요한 위치를 차지한다.

for 문의 백스테이지에 대해

for 문은 일반적인 언어에서의 대표적인 반복문이다. C언어에서는 다음과 같이 쓰인다. 아래 코드는 0~9까지의 정수를 출력하는 예이다.

int i;
for(i=0;i<9;i++){
    printf("%d\n", i);
}

그런데 잘 살펴보면 이때의 for 문은 사실 while 문의 변형에 가깝다. 왜냐하면 for(i=0;i<9;i++) 이라는 구문 자체가 ” i의 초기값은 0인데 i가 9보다 작은 동안 1씩 증가시켜가면서 반복”한다는 의미이기 때문이다.

반면 파이썬에서는 이러한 조건을 만족하는 동안 반복하는 반복문은 오직 while 문만 존재한다. 파이썬의 for 문은 조금 특별한데, 위에서 언급한 리스트, 튜플, 문자열, 사전등의 타입에 대해서 “각 원소에 대해 서 반복한다”라는 단순한 규칙만으로 동작한다. 따라서 0~9까지를 반복하는 위 코드는 파이썬에서는 다음과 같이 쓰인다.

for i in range(9):
  print(i)

## 혹은

for i in [0,1,2,3,4,5,6,7,8,9]:
  print(i)

파이썬의 for 문은 그렇다면 실제로는 어떻게 동작하는 것일까?

for 문과 반복자

파이썬에서는 “반복자”라는 특별한 성질을 가진 타입들이 있다.1 반복자는 영어로 iterator라고 하는데, 파이썬에서 반복자는 기술적으로는 단순히 __next__() 라는 메소드를 가지고 있는 객체를 말한다. (사실 반복자 그 자체는 제너레이터의 일종으로 볼 수도 있고, 기술적으로 제너레이터이다라고 해도 틀린표현은 아니다.) 보통 반복자들은 일련의 연속적인 값 혹은 특정한 규칙을 가진 수열을 계산하는 값을 내부에 가지고 있고, 이를 __next__() 가 호출될 때마다 내부적으로 관리하는 수열의 다음항을 리턴할 수 있는 객체가 된다.

참고로 내장함수의 도움말을 살펴보면 다음과 같다. 여기서는 iterator 라는 표현을 직접적으로 쓰고 있으며, 반복자로부터 다음번 아이템 얻어 리턴한다고 쓰여있다.

In [1]: next?
Docstring:
next(iterator[, default])

Return the next item from the iterator. If default is given and the iterator
is exhausted, it is returned instead of raising StopIteration.
Type: builtin_function_or_method

참고로 반복자가 더 이상 만들어 낼 다음번 항이 없는 경우에는 StopIteration 예외를 일으키고, 이는 곧 순회(iteration)의 끝을 의미한다.

반복가능 (iterable) 프로토콜

파이썬에서 반복가능하다는 말은 결국 for ... in 문에 적용가능하다는 말과 동치이고, 기술적으로는 이터레이터(iterator, 반복자)를 가지고 있는 객체라는 의미이다. 반복가능한 타입의 객체로부터 반복자를 얻어내는 내장함수는 iter() 함수이다. (뒤에서 살펴보겠지만, next(x)x.__next__()를 호출하듯이, iter(x)x.__iter__()를 호출한다. 사실 이것이 반복가능 프로토콜의 핵심이다. )역시 반복 가능한 객체에 대한 힌트를 얻기 위해서 이 함수의 도움말을 살펴보도록 하자.

In [4]: iter?
Docstring:
iter(iterable) -> iterator
iter(callable, sentinel) -> iterator

Get an iterator from an object. In the first form, the argument must
supply its own iterator, or be a sequence.
In the second form, the callable is called until it returns the sentinel.
Type: builtin_function_or_method

우리는 여기서 많은 것을 볼 수 있다.

  1. iter 함수는 어떤 객체로부터 반복자를 얻어서 리턴한다.
  2. 반복가능한 객체는 반복자를 이미 가지고 있거나, 아니면 그 스스로가 연속열이다.
  3. 반복자 뿐만 아니라 특정한 종결값을 리턴할 때까지 계속 어떠한 값을 리턴할 수 있는 함수도 반복가능으로 취급한다.

지금까지의 힌트를 취합하면 다음과 같은 사실들을 알아내었다고 정리할 수 있다.

  1. iter() 함수를 이용하면 iterable한 객체의 반복자를 얻을 수 있다.
  2. next()  함수를 이용하면 반복자의 매 항을 얻을 수 있다.
  3. next() 함수를 이용했을 때 반복자가 더 이상 내 줄 값이 없으면 StopIteration 예외를 일으킨다.
  4. 그리고 range() 함수가 리턴하는 객체는 for ... in 문에 사용할 수 있으니, iterable 하다.

for … in 루프의 구조

그러면 이러한 사실로부터 우리는 파이썬의 for ... in 문을 while 문으로 재구성해볼 수 있다.

for i in range(3):
  print(3)

위 반복문은 우리가 알아낸 사실에 근거하여 다음과 같이 쓸 수 있다.

## range(3)은 iterable 한 객체를 리턴하고
## iter() 함수를 이용하면 그로부터 반복자를 얻을 수 있다. 
x = iter(range(3)) 

while True:
  try:
    i = next(x) ## 반복자로부터 다음 항을 얻는다. 
    print(i)
  except StopIteration: ## 반복자가 고갈되면 StopIteration이 뜬다.
    print("iteratrion finished.")
    break

# 0
# 1
# 2
# iteration finished.

그리고 파이썬의 for ... in 문은 실제로 이렇게 돌아간다. 임의의 리스트나 문자열, 튜플 등에 대해서 아래와 같이 반복자를 직접 얻어서 next() 함수를 계속 호출해볼 수 있다.

In [8]: x = iter([1,2,3,4]) 
 
In [9]: x 
Out[9]: <list_iterator at 0x222958f2b38> 
 
In [10]: next(x) 
Out[10]: 1 
 
In [11]: next(x) 
Out[11]: 2 
 
In [12]: next(x) 
Out[12]: 3 
 
In [13]: next(x) 
Out[13]: 4 
 
In [14]: next(x) 
--------------------------------------------------------------------------- 
StopIteration Traceback (most recent call last) 
<ipython-input-14-5e4e57af3a97> in <module>() 
----> 1 next(x) 
 
StopIteration:

반복자

그렇다면 이번에는 반복자에 대해서 좀 더 알아보도록 하자. 반복자는 앞서 말했듯이 __next__() 메소드를 가지고 있는 객체라고 했다. 내장함수 next()는 모종의 약속에 의해서 인자로 받는 객체의 __next__() 메소드를 호출하고 그 결과를 리턴해주는 역할을 할 뿐이다.

커스텀 반복자 클래스

그렇다면 예를 들어서 피보나치 수열의 항을 순차적으로 리턴할 수 있는 반복자를 만들 수 있지 않을까?

class FibonacciSeq:
  def __init__(self):
    self.a, self.b = 0, 1

  def __next__(self):
    self.a, self.b = self.b, self.a + self.b
    ## 무한 수열이 될 수 있으니, 200보다 큰 값이 만들어지면 끝낸다. 
    if self.a > 200:
      raise StopIteration
    return self.a

## 테스트
f = FibonacciSeq()
next(f)
# 1
next(f)
# 1
next(f)
# 3
next(f)
# 5
next(f)
# 8

대략 성공적이다. 하지만 이렇게 반복자를 만들더라도 iterable한 객체는 따로 존재한다. 즉 지금까지는 itrable한 객체가 있고, 여기에 iter() 함수를 통해서 반복자를 만들어서 각 항을 순회했다는 것이다. 즉 for ... in 문에서 필요한 것은 반복자 그 자체가 아닌 반복자를 생성할 수 있는 객체, 즉 iterable 한 객체이다.

하지만 방금 작성한 FibonacciSeq 클래스는 그 자체가 반복자이면서 반복가능한 객체가 될 수 있다. 왜냐하면 next()2함수와 마찬가지로 iter() 함수는 인자로 받은 객체에 대해서 __iter__() 메소드를 호출하여 반복자를 얻기 때문이다. 이 역시 모종의 약속이 미리 정해져 있는 셈이다. 어쨌든 그 스스로가 반복자의 모든 요건을 갖추고 있으므로 __iter__()는 그 자신을 리턴하는 것으로만 간단히 정의하면 된다.

따라서 다음과 같이 피보나치 생성 클래스를 수정해보자. 수정하는 김에 한계값 자체는 생성시에 인자로 받을 수 있게끔 함께 변경한다.

class FibonacciSeq:
  def __init__(self, upto=200):
    self.limit = upto
    self.a, self.b = 0, 1

  def __iter__(self):
    return self

  def __next__(self):
    self.a, self.b = self.b, self.a + self.b
    if self.a > self.limit:
      raise StopIteration
    return self.a

## 테스트 : 이제 for ... in 문에서도 잘 작동한다.
for f in FibonacciSeq(200):
  print(f)

반복가능한 객체

내부에 __iter__(), __next__() 메소드를 가지는 객체를 만들기만 하면 이 객체는 기술적으로는 반복가능한 객체가 된다고 했다. 이를 통해서 특정한 하나의 값을 반복하는 리피터라든지, 여러 개의 연속열을 한꺼번에 순회할 수 있는 체인시퀀스 같은 도구를 만들 수도 있을 것이며, 그외에 내부 속성들을 for … in 문을 통해서 순회할 수 있는 객체도 디자인할 수 있을 것이다. 예를 들면 어떤 학생들의 시험 성적을 관리하는 코드에서 Student라는 클래스를 만들고 이 클래스 내부에 eng, math, sci 등 과목들의 점수를 저장했을 때, __next__()  메소드에서 미리 정한 순서에 따라 해당 속성값을 리턴하도록 한다면 Student 클래스의 인스턴스는 for ... in 문을 통해서 개별 과목의 점수를 순회할 수 있을 것이다.

iterable 프로토콜이 쓰이는 다른 예

내장함수 중에 sum()이라는 엄청 유명한 함수가 있다. 숫자로 된 리스트나 튜플에서 그 합계를 얻는 함수이다. 아무래도 함수 이름 자체가 하는 일을 너무나 명확하게 이야기하고 있기 때문에 이 함수의 도움말을 읽어보는 사람은 거의 없는 것 같다.  아무튼 읽어보자면, 아래와 같이 이제는 익숙한 이름을 찾을 수 있다.

In [28]: sum?
Signature: sum(iterable, start=0, /)
Docstring:
Return the sum of a 'start' value (default: 0) plus an iterable of numbers

When the iterable is empty, return the start value.
This function is intended specifically for use with numeric values and may
reject non-numeric types.
Type: builtin_function_or_method

즉 iterable한 객체는 각 항 혹은 원소가 덧셈을 지원하는3 객체인 경우에 그 합계를 구할 수 있다는 것이다. 따라서 조금 전에 만든 피보나치 수열 생성기라든지, 예로 잠깐 언급만한 Student의 경우 sum을 쓰면 개별 학생의 총점을 낼 수 있게 된다.

앞서 만든 피보나치 수열 생성기를 이용하면 400보다 작은 피보나치 수열의 항의 합계는 다음과 같이 구해진다.

sum(Fibonacci(upto=400))
# 986
# (1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377)

반복자를 만드는 좀 더 손쉬운 방법

앞서 만든 피보나치 수열 생성기는 클래스를 통해서 만들었는데, 클래스를 통해서 간단한 객체를 생성하는 것은 사실 매우 번거로운 일이다.  파이썬에서는 이러한 기능을 쉽게 구현하는 두 가지 방편을 제공하고 있다.

  1. 연속열은 그 자체로 반복 가능하다. 만약 커스텀 클래스에서 특정한 속성값들을 정해진 순서대로 순회하고 싶다면, __iter__ 메소드를 구현할 때 해당 속성들을 리스트로 묶어서 리턴하면 된다.
  2. 특정한 규칙에 의해서 수열을 만드는 경우에는 제너레이터를 쓸 수 있다.

제너레이터 – 반복자를 만드는 함수

제너레이터는 반복자를 쉽게 만드는 함수이다. 예를 들어 피보나치 수열을 다음과 같은 함수에서 만든다고 생각해보자.

def fib(n=200):
  a, b = 0, 1
  while True:
    a, b = b, a + b
    if a > n:
      raise StopIteration
    return a

이 함수는 첫 번째 항인 1을 리턴하고는 더 이상 동작하지 않는다. 왜냐하면 return 키워드는 함수내에서의 실행 흐름이 종료되었다는 것을 의미하며, 따라서 현재 스레드는 해당 함수를 빠져나오고, 함수 내에서 사용하던 로컬 스코프의 모든 값들은 파괴되기 때문이다.

따라서 함수의 범위를 벗어나지 않으면서 계속 반복할 수 있는 구조가 필요한데, 이를 파이썬에서는 yield 라는 키워드로 지원해준다. yield 키워드를 써서 피보나치 수열 생성함수를 다시 쓰면 아래와 같다.

def fib(n=200):
  a, b = 1, 1
  while a <= n:
    yield a
    a, b = b, a + b

yield a next()가 호출되었을 때 a 값을 전달해준다는 의미이다. 재밌는 점은 값을 리턴해주는 것 같은 항 뒤에 다음항을 계산하는 구문이 온다는 것이다.

제너레이터 함수가 일반함수와 다른 점은 return 키워드 대신에 yield 키워드를 쓰는 것 밖에 없다. 이렇게 yield 키워드가 본체에 쓰여진 함수를 만나면 파이썬은 이 함수가 제너레이터라고 판단하고, 해당 함수의 본체를 이용해서 반복자를 자동으로 생성한다. 그래서 실제로 이 함수가 return 하는 시점에 StopIteration 예외를 일으키는 반복자 객체를 생성하여 리턴한다.

In [37]: x = fib()

In [38]: x
Out[38]: <generator object fib at 0x0000022294F5EC50>

그리고 이 반복자 객체는 __next__()__iter__() 메소드를 모두 구현하고 있는 것으로 보인다. 따라서 for ... in 구문은 물론 sum() 과 같은 내장 함수와도 잘 동작한다.

제너레이터 함수의 특징

제너레이터 함수가 만드는 객체는 일종의 함수처럼 동작하는 동시에 next()로 하여금 값을 요청하는 측에 값을 넘겨 준 후에도 실행 흐름을 잃지 않고 마치 “일시정지”한 것처럼 보인다. 따라서 메인 루틴과 별개로 진행과 일시정지를 반복하면서 별개의 흐름을 유지하는 코루틴으로도 사용되는 경우가 많다. 실제로 단순한 역할을 담당하는 간단한 객체를 디자인하는 경우 클래스보다 코루틴을 사용하면 코드 작성량 및 룩업 절차를 간소화할 수 있는 장점도 있다.

또한 합계나 개수를 구하거나 단순히 반복문에 사용하는 용도라면 전체 시퀀스의 데이터가 필요한 것이 아니라 매 반복에 사용될 값이 순차적으로 필요할 따름이고, 이 경우라면 무식하게 전체 데이터를 메모리에 올려둘 필요가 없다. 파이썬2에서 range() 함수는 기본적으로 리스트를 생성했고, 이를 제너레이터로 대체하는 xrange() 함수가 있었다. 파이썬3로 이행하면서 xrange() 함수는 없어졌지만, range() 함수 자체가 제너레이터를 생성하는 함수로 바뀌었다.

아마 이전 파이썬에서 리스트를 사용했던 것은 for 문에서의 성능 문제인데 (매번 __next__()를 호출하는데 드는 비용이 적지 않다.) 오히려 파이썬3에서의 순회는 일반 연속열보다 제너레이터쪽이 더 빠르다.

보너스 – 제너레이터 표현식

리스트의 축약 문법에 대해서는 다들 많이 쓰고 있을 것이라고 생각한다.  그런데 여기서 대괄호 대신에 보통 괄호를 쓰면 이 리스트 축약 리터럴은 제너레이터 표현식이 된다. 제너레이터 표현식으로 쓰여진 식은 메모리상에 모든 원소가 즉시 계산되어 원소로 만들어지는 리스트 축약과는 달리 제너레이터로 만들어지며, 각 항의 계산은 필요한 시점에 lazy하게 계산된다. 예를 들어 100,000 보다 작은 모든 3 또는 5의 배수의 합을 구한다고 생각해보자.

>> print(sum([x for x in range(100_0000) if x % 3 is 0 or x % 5 is 0]))
233333166668
>> print(sum((x for x in range(100_0000) if x % 3 is 0 or x % 5 is 0)))
233333166668

두 코드의 값은 완전히 같지만,  작동하는 과정은 완전히 다르다. 첫번째 코드는 리스트 축약을 사용했고, 이 과정에서 리스트가 만들어진다. (정수 466667개를 포함한다.) 이 리스트의 크기는 대략 4메가 바이트가 안되는 수준이다. 두 번째 예제에서는 제너레이터가 하나 만들어지며, 이 과정에서 소비되는 메모리는 88바이트이다.

참고로 sum() 등의 괄호 안에 들어가는 제너레이터 표현식은 바깥에 괄호가 있는 관계로 아예 괄호를 빼 버릴 수도 있다.

이상으로 파이썬의 반복 가능 프로토콜에 대해서 알아본 내용이었다.


  1. 사실 이 개념은 “상이한 여러 타입들이 공통된 동작을 보유한다”는 의미에서 하스켈의 타입 클래스나 Objective-C/Swift의 프로토콜과 유사한 개념이다. 파이썬에서는 언어적 차원에서 이러한 프로토콜 문법을 지원하고 있지는 않지만, 많은 구현에서 프로토콜을 도입해서 의존하고 있다. 
  2. 앞에서도 언급했듯이 next() 내장함수는 객체의 __next__() 메소드를 호출한다. 이와 비슷한 동작은 내장함수 뿐만 아니라 연산자에서도 적용된다. 예를 들어 + 연산은 객체 내의 __add__(obj)를 호출하여 두 값을 더 한 새로운 객체를 리턴하는 함수로 볼 수 있다. 
  3. 사실 정수(int)와 실수(float)타입의 숫자값도 파이썬에서는 해당 타입의 인스턴스 객체이다. 따라서 1, 2와 같은 정수 값도 사실은 __add__(obj) 와 같은 메소드를 가지고 있는 개별 정수이며, 실제로 1 + 21.__add__(2) 와 같은 식으로 동작한다. 

파이썬의 제너레이터와 코루틴

파이썬 제너레이터는 특별한 종류의 함수 객체이다. 함수 내부에서 yield 구문을 사용하여 특정 값을 내놓은 후에도 실행을 종료하지 않아 제거되지 않고, 다시 그 자리에서부터 이어서 계산을 반복하고 다시 값을 내놓을 수 있다. rangemap, filter 등의 객체가 제너레이터의 일종이라고 할 수 있다.

아주 먼 옛날, 파이썬 2.5에서 제너레이터에 특별한 기능이 생겼는데, 바로 제너레이터 속으로 값을 전달하는 기능이다. (PEP342) 이는 매우 흥미로운 패턴으로 이어지게 되는데, 실행 중 한 번 yield 문을 만나 자신의 위치를 기억하고 있다가 다시 그 자리에서 실행이 가능하다는 점에서 두 개 이상의 제너레이터가 서로 값을 주고 받으면서 교차식으로 실행하는 것이 가능하다. 이는 일반적인 함수 호출의 패턴인 주 루틴 – 서브 루틴의 관계와 달리 두 개의 루틴이 함께 실행된다는 부분에서 코루틴(coroutine)이라고 부른다.

코루틴은 사실 완전히 새롭게 등장한 개념은 아니었다. 이미 6~70년대에 기반이 닦여진 기술이이었다. 당시에는 작업 흐름의 분산을 위한 여러 가지 개념들이 도입되고 시도되고 있었는데, 이 당시에 이러한 기술들 중에서 가장 환호를 받았던 것은 다름 아닌 멀티스레드였다.

멀티스레드가 큰 인기를 얻고 발전해 나가면서 상대적으로 코루틴은 거의 방치되다 시피하였으나, 규모가 커짐에 따라 멀티 스레드는 자원 경쟁이라든지 동기화문제 등 더 큰 골칫거리를 가져왔다. 이러한 문제로 인해 코루틴 개념은 그린릿(greenlet)이나 경량스레드(lightweight thread)라는 이름으로 재발견되어 주목받는 경우도 있다.

코루틴이 흥미로운 지점은 멀티스레드 없이 하나의 스레드 위에 여러 개의 실행흐름이 존재할 수 있다는 것이다. 즉 멀티스레드에서의 골치 아픈 문제들을 끌어들이지 않고서도 실행 흐름을 분산할 수 있다는 것을 의미한다.

하지만 분산처리와 같은 상황이 아니더라도 제너레이터 혹은 코루틴은 파이썬 프로그래밍에서 매우 중요한 비중을 차지한다. 멀티스레드와 같은 적극적인 동시성이 아니더라도, 제너레이터의 ‘느긋한(lazy)’한 특성은 실제로는 concurrent 하지 않은 작업들을 마치 동시에 진행되는 것처럼 다룰 수 있게 하며, 무거운 연산을 가능한 뒤로 미루어 실행 시간내의 체감 퍼포먼스가 좋은 것처럼 보일 수 있게 한다. (사실 이정도면 충분한 것이, 파이썬의 멀티스레드는 실제로는 동시에 실행되지 않기 때문이다.)

이 외에도 제너레이터와 코루틴은 간단한 클래스를 대체할 수 있으며, 일련의 처리 과정에서의 단위 작업을 구성하고 이들을 선언적으로 연결하는 방법으로도 활용할 수 있다. 오늘 이 글에서는 제너레이터와 코루틴에 대해 알아보도록 하자.

파이썬의 제너레이터와 코루틴 더보기

Task, Future, Coroutine

코루틴과 Task에 대한 내용을 발행했었는데 이 부분은 사실 asyncio에 대한 총정리 글에 포함되는 내용이었던 관계로, asyncio 에서 사용되는 세 가지 대기 가능 객체인 Task, Future, Coroutine 의 차이에 대해서 설명하는 내용으로 수정합니다.


asyncio는 비동기 처리를 위해 비동기 코루틴을 만들고 이를 스케줄링하여 실행하는 기능을 중심으로 구성되어 있다. 그런데 관련 함수를 찾아보면 어떤 것은 코루틴, 또 어떤 것은 Task나 Future로 표현하며 섞어 쓰는 것 같기도 해서 혼란스러운 점이 있다. 이 글에서는 코루틴과 asyncio.Task, asyncio.Future 가 각각 어떻게 다른지 살펴보도록 하겠다.

Task, Future, Coroutine 더보기