Python 제너레이터: 효율적 이터레이션과 고급 활용
Python 제너레이터
Python 제너레이터가 이터레이션을 어떻게 단순화하고, 보일러플레이트 줄이며, 기존 이터레이터 대비 메모리 사용량 개선하는지 알아보기.
제너레이터란?
Python에서 커스텀 이터레이터 만들려면 보통 __iter__()와 __next__() 구현한 클래스 필요, 내부 상태 관리, 완료 시 StopIteration 발생. 제너레이터는 간단한 함수 작성으로 값 하나씩 산출하게 해 이 보일러플레이트 제거.
간단히 말해 제너레이터는 이터레이터 반환 함수. 함수 본문이 지연 실행돼 각 yield 후 일시정지, 후속 호출 시 재개.
Python에서 제너레이터 만들기
제너레이터 정의는 일반 함수 쓰고 return 대신 yield 쓰면 그만:
def my_gen():
n = 1
print('This is printed first')
yield n
n += 1
print('This is printed second')
yield n
n += 1
print('This is printed at last')
yield n
my_gen() 호출하면 next() 호출될 때만 실행 시작하는 이터레이터 반환.
a = my_gen()
next(a) # 첫 메시지 출력 후 1 반환
next(a) # 두 번째 메시지 출력 후 2 반환
next(a) # 마지막 메시지 출력 후 3 반환
next(a) # StopIteration 발생
로컬 변수는 yield 사이 지속되지만 제너레이터는 한 번만 순회 가능. 다시 시작하려면 새 인스턴스 생성.
제너레이터도 이터레이터라 직접 루프 가능:
for item in my_gen():
print(item)
자주 쓰는 제너레이터 패턴
문자열 뒤집기
def rev_str(s):
for i in range(len(s)-1, -1, -1):
yield s[i]
for char in rev_str('hello'):
print(char)
리스트, 튜플 등 모든 이터러블에 동작.
제너레이터 표현식
리스트 컴프리헨션 비슷하게 괄호로 익명 제너레이터 생성:
my_list = [1, 3, 6, 10]
# 리스트 컴프리헨션
squares_list = [x**2 for x in my_list]
# 제너레이터 표현식
squares_gen = (x**2 for x in my_list)
print(squares_list)
print(squares_gen)
제너레이터는 값 온디맨드 산출:
a = (x**2 for x in my_list)
print(next(a)) # 1
print(next(a)) # 9
print(next(a)) # 36
print(next(a)) # 100
next(a) # StopIteration
제너레이터 표현식은 sum이나 max 같은 함수에 괄호 없이 직접 전달 가능.
왜 제너레이터 쓰나?
- 간결한 구현: 장황한 이터레이터 클래스 몇 줄 코드로 대체.
- 메모리 효율: 한 번에 한 아이템 생성, 대용량/무한 시퀀스에 이상적.
- 무한 스트림: 끝없는 데이터 소스 표현(
while True: yield).
- 파이프라인 구성: 여러 제너레이터 체이닝으로 깔끔하고 읽기 쉬운 데이터 처리.
예: 2의 거듭제곱
def pow_two_gen(max_n=0):
n = 0
while n < max_n:
yield 2 ** n
n += 1
예: 피보나치 수 제곱 합
def fibonacci_numbers(count):
a, b = 0, 1
for _ in range(count):
a, b = b, a + b
yield a
def square(nums):
for n in nums:
yield n ** 2
print(sum(square(fibonacci_numbers(10)))) # 4895
제너레이터는 다양한 이터레이션 시나리오에 우아하고 효율적이며 유지보수 쉬운 코드 작성 가능케 합니다.