본문 바로가기
개발/python

[Python] generator와 yield

by 유다110 2019. 2. 18.
반응형

# generator란?

파이썬의 generator란 yield라는 키워드를 사용하는 iterator 생성 함수이다.


* iterator란 countable한 값들을 가지고 있는 객체이다. 이 iterator가 iterate 된다는 것은 모든 값을 가로지를 수 있음을 뜻한다. 파이썬에서 iterator는 iterator 프로토콜을 시행하는 객체이며, __iter__()와 __next__() 메소드를 가지고 있다.



# generator 함수 생성

generator를 만드는 방법은 간단하다. 평범한 함수에서 return 대신 yield 키워드를 사용하면 된다. 함수가 하나 이상의 yield를 가지고 있다면 generator 함수가 된다. 

한편 yield와 return은 모두 같은 값을 리턴하지만, return은 함수를 종료시키는 데 반해 yield는 함수를 정지시키고 현 상태를 저장한다. 그리고 그 함수가 연이어 실행될 때 저장되어 있는 데이터를 사용한다. 이때 __iter__()나 __next__() 같은 iterator 메소드들이 자동으로 시행된다. next()를 사용하여 해당 아이템을 반복(iterate)할 수도 있다.



# generator를 사용해 소인수분해하기

어느 기술/기능이나 그렇듯이 원리를 공부해도 사용해보지 않으면 감이 잘 잡히지 않아 전에 짰던 코드들을 새롭게 짜보았다.

예시가 될 녀석은 프로젝트 오일러의 2번 문제 '피보나치 수열에서 400만 이하의 짝수인 항 더하기'이다. 

첫 코드를 보자. 잘 기억은 안 나지만 3년 전에 이 문제를 처음 풀 때 이렇게 짰던 것 같다.

def fibo(n):
    if n <= 1:
        return n
    else:
        return fibo(n-1) + fibo(n-2)

print(sum([fibo(i) for i in range(2, 4000001)]))

당연히 될리가 없는 식이다. 식 자체가 틀린 건 아니지만 피보나치 수열 하나하나를 알아야 할 때마다 처음부터 다시 계산하고 있으니 30만 넘어가도 1초가 넘게 걸린다. 또 400만 개의 루프를 도는 동안 계속해서 메모리를 할당/해제할 것이다.


그럼 다음으로 고친 코드를 보자.

def fibo(n):
    result = 0
    a, b = 0, 1
    while a <= n:
        if a % 2 == 0:
            result += a
        a, b = b, a+b
    return result

print(fibo(4000000))

위 식은 성능면에서는 문제가 없지만 generator를 사용하면 더 직관적이고 예쁜 코드를 짤 수 있다.

def fibo(n):
    a, b = 0, 1
    while a <= n:
        if a % 2 == 0:
            yield a
        a, b = b, a + b

print(sum(list(fibo(4000000))))

사실 성능 자체는 두 번째 식과 비슷하다. 혹시 몰라 400만을 4해까지 늘려봤는데 둘 다 항상 0초가 나온다.

그래도 generator 함수를 사용한 코드는 yield로 변수 a의 값을 저장해둠으로써 result 변수를 사용하지 않게 되었다.

피보나치 수열 함수은 매우 간단해서 변수가 하나 줄고 늘고 하는 게 아무렇지 않아보일 수 있지만 복잡한 식에서는 꽤 유용할 듯 하다.정신승리

반응형

댓글