[python] 제너레이터 사용하기

제너레이터와 yield 알아보기

# 이번에는 제너레이터(발생자)에 대해 알아보자.
# 제너레이터는 함수 안에서 yield 라는 키워드만 사용하면 끝이다.
# yield 값
# 위와 같은 형식으로 사용한다.
# yield를 이용해서 제너레이터를 만들고 사용해보자.
def number_generator():
    yield 0
    yield 1
    yield 2


for i in number_generator():
    print(i, end=' ')  # 0 1 2 가 출력된다. 이터레이터와 사용 방법이 똑같다.
print()

# number_generator 함수로 만든 객체가 이터레이터인지 dir 함수를 이용해 확인해보자.
g = number_generator()
print(g)  # <generator object number_generator at 0x0000020265C4E7B0>
print(dir(g))  # [..., '__iter__', ..., '__next__', ...]
# 실제로 이터레이터에서 볼 수 있는 __iter__, __next__ 메소드가 들어있다.
# 이 객체에 대해 __next__를 호출해보면 0, 1, 2가 나오다가 StopIteration 예외가 발생한다.
try:
    while True:
        print(g.__next__(), end=' ')
except StopIteration:
    print('StopIteration')
# 0 1 2 StopIteration 가 출력된다.
# 이처럼, 함수에 yield만 사용해서 간단하게 이터레이터를 구현할 수 있다.
# 제너레이터는 제너레이터 객체에서 __next__ 메소드를 호출할 때마다
# 함수 안의 yield까지 코드를 실행하며 yield에서 값을 발생시킨다.
# yield는 생산하다라는 뜻 뿐만 아니라 양보하다 라는 뜻도 가지고 있다.
# 즉, yield를 사용하면 값을 함수 바깥으로 전달하며 코드 실행을 함수 바깥에 양보한다.
# 따라서 yield는 현재 함수를 잠시 중단하고 함수 바깥의 코드가 실행되도록 만든다.

# 제너레이터는 함수 끝까지 도달하면 StopIteration 예외가 발생한다.
# 마찬가지로 return도 함수를 끝내므로 return을 사용해서 함수 중간에 빠져나오면 StopIteration 예외 발생
# 제너레이터 안에서 return에 반환값을 지정하면 발생하는 StopIteration 예외의 에러 메시지로 들어간다.

제너레이터 만들기

# 이번에는 range(n)처럼 동작하는 제너레이터를 만들어보자.
def number_generator(stop):
    n = 0               # 0부터 시작
    while n < stop:     # 현재 숫자가 stop보다 작을 때동안 반복
        yield n         # 현재 숫자를 바깥으로 전달
        n += 1          # 현재 숫자 증가


for i in number_generator(3):
    print(i, end=' ')  # 0 1 2
print()

# yield에서 다른 의미있는 값을 반환하는 것으로 응용해보자.
def upper_generator(x):
    for i in x:
        yield i.upper()


alphabets = ['a', 'b', 'c', 'd', 'e']
for i in upper_generator(alphabets):
    print(i, end=' ')  # A B C D E
print()

# 이처럼 yield를 잘 이용하면 __iter__과 __next__를 구현해서 이터레이터를 만드는 것보다 훨씬 간단하게 구현을 할 수 있다.

yield from으로 값을 여러번 바깥으로 전달하기

# 여태는 yield로 값을 한 번씩 바깥으로 전달했기 때문에 값을 여러 번 전달할 때에는 반복문을 이용했다.
# 이런 경우에 반복문을 쓰지 않고 yield from을 사용하면 된다.
# yield from 반복가능한 객체
# yield from 이터레이터
# yield from 제너레이터 객체
# yield from을 이용해서 리스트의 숫자들을 바깥으로 전달해보자.
def number_generator():
    x = [1, 2, 3]
    yield from x


for i in number_generator():
    print(i, end=' ')  # 1 2 3 이 출력된다.
print()

# 리스트 표현식을 사용할 때 [] 를 이용했는데,
# 같은 리스트 표현식을 ()로 묶으면 제너레이터 표현식이 된다.
# 리스트 표현식은 처음부터 리스트의 요소를 만들어내지만 제너레이터 표현식은 필요할 때 만드므로 메모리를 절약할 수 있다.
print([i for i in range(10) if i % 2 == 0])  # [0, 2, 4, 6, 8]
print((i for i in range(10) if i % 2 == 0))  # <generator object <genexpr> at 0x00000200FE639510>

Updated:

Leave a comment