본문 바로가기

프로그래머/Python

[윤성우의 열혈 파이썬 중급편] 09. generator 함수

출처 : 윤성우의 열혈 파이썬 : 중급

09. generator 함수

  • generator은 iterator 객체의 한 종류
  • generator를 만드는 두가지 방법
    • generator 함수(function)
    • generator 표현식(expression)

함수 기반의 제너레이터

def gen_num():
    print('first number')
    yield 1
    print('second number')
    yield 2
    print('third number')
    yield 3

gen = gen_num() # generator 객체 생성
type(gen)       # <class 'generator'>

next(gen)   # first number \n 1
  • gen_num이 일반 함수라면 그 안에 있는 모든 내용이 실행되나, 이 경우에는 하나도 실행되지 않는다
  • 대신 'generator object'가 만들어져 반환된다
  • generator object를 전달하며 next 함수를 호출하면 함수의 첫 번째 문장부터 시작해서 첫 번째 yield 문을 만날 때까지 실행을 이어간다
  • 이 때 yield 는 return 의 역할을 하게되어 숫자 1을 반환
  • 마지막 yield문이 실행되고 다시 next함수를 호출하면 StopIteration 예외 발생
  • 이렇듯 함수 호출 이후에 그 실행의 흐름을 next 함수가 호출될 때까지 미루는(늦추는) 특성을 가리켜 lazy evaluation이라 한다

generator 함수 내 for 문

def gen_for():
    for i in [1, 2, 3]:
        yield i

g = gen_for()
next(g)     # 1
next(g)     # 2
next(g)     # 3

제너레이터가 갖는 장점

def pows(s):
    r = []
    for i in s:
        r.append(i ** 2)
    return r

st = pows([1, 2, 3, 4, 5, 6, 7])
for i in st:
    print(i, end = ' ')

import sys
sys.getsizeof(st)   # 100

def gpows(s):
    for i in s:
        yield i ** 2

st = gpows([1, 2, 3, 4, h5, 6, 7])
for i in st:
    print(i, end = ' ')

sys.getsizeof(st)   # 64
  • 위의 예에서 사용하는 메모리 공간의 크기는 리스트의 길이에 비례해서 늘어남
  • 하지만 generator를 사용하는 경우 리스트의 길이에 상관 없이 사용하는 메모리의 공간의 크기가 동일
  • generator object는 반환할 값들을 미리 만들어서 저장해 두지 않기 때문

생성되는 값들을 순서대로 하나씩 가져다 쓰면 되는 상황에서는 제너레이터를 기반으로 코드를 작성하는 것이 합리적

map & filter도 generator 함수이다

yield from

def get_nums():
    ns = [0, 1, 0, 1, 0, 1]
    for i in ns:
        yield i

g = get_nums()
next(g)     # 0
next(g)     # 1

# 다음과 같이 간단히 쓸 수 있다
def get_nums():
    ns = [0, 1, 0, 1, 0, 1]
    yield from ns

g = get_nums()
next(g)     # 0
next(g)     # 1