본문 바로가기
Python/기초

파이썬 제너레이터(Generator) 한 글로 정리하기

by 모두의 케빈 2023. 8. 29.

 

 

제너레이터(Generator)란?


Generator

 

제너레이터는 이터레이터를 생성해 주는 특별한 '함수'입니다. 따라서 모든 제너레이터는 이터레이터입니다. (반대는 성립하지 않음)

 

제너레이터는 아래와 같은 특징이 있습니다.

 

 

제너레이터의 특징

1. 함수이지만, return 구문 대신 매직 키워드 yield 구문을 사용

2. 메모리 효율적

3. 계산이 필요할 때까지 계산을 늦추는 Lazy Evaluation 효과가 있습니다.

 

파이썬 코드를 보면 종종 제너레이터를 사용하는 경우를 볼 수 있는데요. 제너레이터를 사용하는 이유에 대해 하나씩 알아보겠습니다.

 

 

제너레이터를 사용하는 이유: 쉽게 iterator를 생성할 수 있다.

 

직접 이터레이터를 선언해야 하는 경우, 이터레이터 클래스를 선언하는 것이 복잡하게 느껴질 수 있습니다. 그러나 제너레이터 함수를 정말 쉽게 이터레이터를 만들 수 있습니다.

 

# 제너레이터 함수로 이터레이터를 쉽게 만들 수 있다.
def mygenerator(iterable):
    cursor =0
    while cursor < len(iterable):
        yield iterable[cursor]
        cursor += 1

myiter2 = mygenerator([1,2,3,4])

print(type(myiter2))
print(isinstance(myiter2, Iterator))
 >>>>> 실행 결과:
<class 'generator'>
True

 

yield 구문은 return과 유사합니다. 함수 실행 도중, yield 구문을 만나면 아래 코드를 실행하지 않고 값을 반환합니다. 단, 이때 Iterator처럼 어디까지 진행했는지에 대한 상태를 기억할 수 있습니다.

 

 

둘째, 메모리 효율적입니다.

 

제너레이터는 메모리 효율적입니다. 리스트와 비교해 보겠습니다. 리스트는 모든 원소를 메모리에 올려두기 때문에 리스트의 원소가 많아질수록 용량이 커집니다.

# 리스트와 제너레이터의 용량 비교

from sys import getsizeof

list_a = [i for i in range(100)]
print(f"작은 크기 리스트의 용량: {getsizeof(list_a)}")

list_b = [i for i in range(10000)]
print(f"큰 크기 리스트의 용량: {getsizeof(list_b)}")
 >>>>> 실행 결과
작은 크기 리스트의 용량: 904
큰 크기 리스트의 용량: 87616

 

 

 

 

단, 제너레이터는 그렇지 않습니다. 제너레이터는 그때그때 필요 시마다 원소를 메모리에 올립니다. 따라서 같은 개수의 원소를 순회한다면, 제너레이터의 메모리 효율이 리스트 대비 훨씬 뛰어납니다.

 

그래서 보통 딥러닝 연산 과정에서 빅데이터를 처리할 때 제너레이터가 활용되기도 합니다.

# 제너레이터는 메모리 효율적
generator_a = mygenerator(list_a)
generator_b = mygenerator(list_b)

print(type(generator_a), type(generator_b))
print(f"작은 크기 제너레이터의 용량: {getsizeof(generator_a)}")
print(f"큰 크기 제너레이터의 용량: {getsizeof(generator_b)}")
 >>>>> 실행 결과
<class 'generator'> <class 'generator'>
작은 크기 제너레이터의 용량: 112
큰 크기 제너레이터의 용량: 112

 

 

 

셋째, 연산 지연 효과

 

마지막으로는 필요시까지 계산을 늦춰주는 효과가 있습니다. 아래의 코드처럼 의도적으로 늦추고 싶은 구문을 yield 뒤에 선언하면 다시 호출하기 전까지 해당 연산을 지연시킬 수 있습니다.

def mygenerator(iterable):
    cursor =0
    while cursor < len(iterable):
        yield iterable[cursor]
        cursor += 1
        print("일단 메모리에 올려뒀다가, 나중에 연산")

 

myiter3 = mygenerator([1,2,3,4])
myiter3.__next__()
 >>>>> 실행 결과: 1

 

myiter3.__next__()
 >>>>> 실행 결과:
일단 메모리에 올려뒀다가, 나중에 연산
2

 

 

댓글