Generator – JavaScript, Python

JavaScript Generator

자바스크립트와 파이썬의 iteration을 살펴본 적이 있다. 이번엔 더욱 사용하기 편하고 읽기 편한 generator를 비교했다. 자바스크립트에서 generator는 아래와 같은 문법을 가지는 특수한 함수다.

function *genFunction() {
  yield 100;
  yield 200;
}

함수 이름 앞에 * 표가 있으며 함수 바디 안에 yield 키워드가 여러개 존재할 수 있다. Yield 키워드는 없어도 되지만 그럴 경우 그냥 함수를 사용하는 편이 혼란스럽지 않을 것이다. 이 함수는 수행되면 객체를 돌려주는데 이 객체는 iterator 객체와 비슷하게 next 함수를 가지고 있으며 next 함수가 돌려주는 값의 형식도 iterator 객체와 동일하다.

const g = genFunction();
undefined
> g.next()
{ value: 100, done: false }
> g.next()
{ value: 200, done: false }
>

g는 next 함수를 가지고 있는 객체다. 자바스크립트에서 iterable 객체는 [Symbol.iterator] 함수를 가지고 있다. 이 함수는 호출되면 next 함수를 가지는 함수를 리턴하는데 next 함수는 여러번 호출 되면서 iterable 객체가 가지고 있는 데이터를 순서대로 돌려준다. Generator를 사용하면 [Symbol.iterator] 함수를 쉽게 만들 수 있다. for of 문에 generator 함수를 사용하여 만든 iterable 객체를 전달할 수 있다.

> for (item of {
...   [Symbol.iterator]: genFunction
... }) {
...   console.log(item);
... }
100
200

이처럼 generator는 iterator 함수를 대체하여 사용할 수 있다. 아래 처럼 for of 문에 generator 객체를 바로 넣을 수도 있다.

> for (item of genFunction()) {
...   console.log(item);
... }
100
200

Generator를 사용하여 생성된 객체는 iterable 객체가 사용될 수 있는 여러 곳에서 사용될 수 있다.

> [...genFunction()]
[ 100, 200 ]
> [a, b] = genFunction()
{}
>
> a
100
> b
200

명시적으로 next 함수를 구현할 필요가 없고 yield 키워드를 사용하여 next 함수가 돌려줄 값을 지정할 수 있는 것이 iterator 대비 장점이다. Yield 키워드는 next 함수의 결과 값을 돌려주고는 함수 수행을 멈춘다. 그다음 next 함수가 호출되면 다음 yield 키워드를 만날 때까지 계속해서 함수를 수행한다.

> function *nextFunction() {
...   let size = yield 100;
...   yield size;
... }
undefined
> const n = nextFunction();
undefined
> n.next()
{ value: 100, done: false }
> n.next(300)
{ value: 300, done: false }

처음 next 함수는 100을 돌려준다. 그다음 next 함수의 인자로 300을 넘기면 이 300 인자는 첫 번째 yield 문의 결과 값이 된다. 그래서 변수 size는 300이 되고 두 번째 yield의 동작으로 300 값이 돌려진다. Generator는 return 문을 만나기 전에 종료되지 않는다 그래서 내부에서 선언된 데이터가 사라지지 않는데 이것도 iterator와 다른 점이다. 내부 데이터를 가지는 다른 generator의 예를 보자.

function *keepFunction() {
  let size = 1;
  while (true) {
    yield size++;
    if (size > 3) return;
  }
}

이 함수의 size 값은 next 함수가 호출될 때마다 증가하고 return 문을 만나면 더 이상 다음 값을 돌려주지 않는다.

> const k = keepFunction();
undefined
> k.next()
{ value: 1, done: false }
> k.next()
{ value: 2, done: false }
> k.next()
{ value: 3, done: false }
> k.next()
{ value: undefined, done: true }

Iterator 함수와 generator 함수는 모두 무한 루프 동작에 주의 해야 한다. 아래와 같은 동작은 “JavaScript heap out of memory” 에러를 유발한다.

function *foreverFunction() {
  while(true) {
    yield 0;
  }
}
[...foreverFunction()]

Python Generator

이제 파이썬에서 어떤 방식으로 generator를 사용하는지 알아보자. 파이썬에서는 yield 키워드를 사용하는 모든 함수가 generator 함수다. 자바스크립트 처럼 * 를 붙여서 generator를 함수를 나타내는 번거로움은 없지만 이 함수가 generator 함수인지 알기 위해선 함수 바디에서 yield 키워드를 찾아야 한다.

>>> def genFunction():
...     yield 100
...     yield 200
...
>>> genFunction()
<generator object genFunction at 0x7f7ba0edd570>

명확하게 generator 객체라고 알려준다. 파이썬의 iterable 객체처럼 for in 문에 사용할 수 있다.

>>> for item in genFunction():
...     print(item)
...
100
200
>>>

Yield 키워드의 동작은 자바스크립트와 동일하다.

>>> gen = genFunction()
>>> next(gen)
100
>>> next(gen)
200

리스트 변신도 문제 없다.

>>> list(genFunction())
[100, 200]

물론 무한 루프 동작은 자바스크립트의 generator와 마찬가지로 주의해야 한다.

def foreverFunction():
    while True:
        yield 0

list(foreverFunction())

위의 코드는 “MemoryError”를 유발한다. 파이썬의 경우에는 yield 키워드를 사용하지 않는 generator 객체 생성 방법을 따로 지원한다. Generator 표현식이라고 한다.

>>> gen = (i for i in [100, 200])
>>> next(gen)
100
>>> next(gen)
200

for in 문에 사용해도 문제 없으며 조금 복잡하게 사용할 수도 있다.

>>> gen = (i*j for i in [100, 200] for j in [1, 2] if i*j != 200 )
>>> for item in gen:
...     print(item)
...
100
400

마무리

두 언어에서 generator는 약간 다른 문법을 가지고 있지만 동작 방식은 비슷하다. Generator가 iterator와 다른 점은 yield 키워드를 사용하여 함수 수행을 멈출 수 있으며 next 함수를 사용하여 다시 수행할 때 데이터를 전달할 수 있다는 점이다. 문법 모양을 봐도 iterator 보다 이해하기 쉬우며 코드가 짧다. Iterable 객체를 사용할 수 있는 곳에 generator 함수가 생성한 객체를 사용할 수 있는 것은 공통점이다.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중

This site uses Akismet to reduce spam. Learn how your comment data is processed.