제너레이터의 정의와 기본 개념
제너레이터는 파이썬에서 이터레이터(iterator)를 생성하는 함수다.
일반 함수와 달리 yield 문을 사용하여 데이터를 하나씩 반환한다.
이는 모든 결과를 메모리에 저장하지 않고, 필요할 때마다 값을 생성할 수 있게 해준다.
제너레이터 함수가 호출되면, 함수 본문이 즉시 실행되지 않는다. 대신, 제너레이터 객체가 반환된다.
이 객체의 next() 메서드가 호출될 때마다 함수는 다음 yield 문까지 실행되고, 해당 값을 반환한다.
- 간단한 제너레이터 함수의 예시
def simple_generator():
yield 1
yield 2
yield 3
gen = simple_generator()
print(next(gen)) # 출력: 1
print(next(gen)) # 출력: 2
print(next(gen)) # 출력: 3
이 예시에서 simple_generator 함수는 세 개의 값을 순차적으로 생성한다. next() 함수를 호출할 때마다 다음 값이 반환된다.
제너레이터의 장점
1. 메모리 효율성
제너레이터는 모든 값을 한 번에 메모리에 저장하지 않고, 필요할 때마다 값을 생성한다. 이는 대용량 데이터셋을 다룰 때 특히 유용하다.
2. 성능 향상
필요한 값만 생성하므로, 전체 시퀀스를 미리 계산하는 것보다 초기 응답 시간이 빠르다.
3. 무한 시퀀스 표현
제너레이터를 사용하면 이론적으로 무한한 데이터 스트림을 표현할 수 있다.
4. 코드 간결성
복잡한 이터레이터 로직을 간단한 함수로 표현할 수 있다.
제너레이터 표현식
제너레이터 함수 외에도, 파이썬은 제너레이터 표현식을 지원한다. 이는 리스트 컴프리헨션과 유사하지만 대괄호 대신 괄호를 사용한다.
# 리스트 컴프리헨션
squares_list = [x**2 for x in range(10)]
# 제너레이터 표현식
squares_gen = (x**2 for x in range(10))
실제 사용 사례
1. 대용량 파일 처리
def read_large_file(file_path):
with open(file_path, 'r') as file:
for line in file:
yield line.strip()
for line in read_large_file('huge_file.txt'):
process_line(line)
이 예시에서 read_large_file 함수는 대용량 파일을 한 줄씩 읽어 처리한다. 전체 파일을 메모리에 로드하지 않고도 효율적으로 처리할 수 있다.
2. 피보나치 수열 생성
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(10):
print(next(fib))
이 제너레이터는 무한한 피보나치 수열을 생성한다. 필요한 만큼만 값을 생성할 수 있어 효율적이다.
3. 데이터 파이프라인 구축
def data_source():
for i in range(100):
yield i
def process_data(data):
for item in data:
yield item * 2
def filter_data(data):
for item in data:
if item % 4 == 0:
yield item
pipeline = filter_data(process_data(data_source()))
for item in pipeline:
print(item)
이 예시는 데이터 소스, 처리, 필터링의 파이프라인을 제너레이터를 사용해 구현한다. 각 단계가 필요할 때만 실행되어 메모리 사용을 최소화한다.
제너레이터의 고급 기능
1. send() 메서드
제너레이터는 send() 메서드를 통해 외부에서 값을 주입받을 수 있다.
def echo_generator():
while True:
received = yield
yield f"Echo: {received}"
gen = echo_generator()
next(gen) # 제너레이터 초기화
print(gen.send("Hello")) # 출력: Echo: Hello
print(gen.send("World")) # 출력: Echo: World
2. throw() 메서드
제너레이터 내부로 예외를 전달할 수 있다.
def number_generator():
try:
yield 1
yield 2
yield 3
except ValueError:
yield 'Error occurred'
gen = number_generator()
print(next(gen)) # 출력: 1
print(gen.throw(ValueError)) # 출력: Error occurred
3. close() 메서드
제너레이터를 강제로 종료할 수 있다.
def countdown():
i = 5
while i > 0:
yield i
i -= 1
gen = countdown()
print(next(gen)) # 출력: 5
print(next(gen)) # 출력: 4
gen.close()
print(next(gen)) # StopIteration 예외 발생
제너레이터와 코루틴
제너레이터는 코루틴의 기반이 된다. 코루틴은 여러 진입점을 가진 서브루틴으로, 비동기 프로그래밍에서 중요한 역할을 한다.
import asyncio
async def async_generator():
for i in range(3):
await asyncio.sleep(1)
yield i
async def main():
async for item in async_generator():
print(item)
asyncio.run(main())
이 예시는 비동기 제너레이터를 사용하여 비동기적으로 값을 생성하고 처리한다.
제너레이터의 성능 고려사항
제너레이터는 많은 경우에 리스트나 다른 시퀀스 타입보다 효율적이지만, 모든 상황에서 최선의 선택은 아니다.
1. 반복 횟수
데이터를 여러 번 반복해야 하는 경우, 제너레이터는 매번 값을 재생성해야 하므로 비효율적일 수 있다.
2. 인덱싱과 슬라이싱
제너레이터는 인덱싱과 슬라이싱을 직접 지원하지 않는다. 필요한 경우 리스트로 변환해야 한다.
3. 메모리 사용
대용량 데이터를 다룰 때 제너레이터가 유리하지만, 작은 데이터셋에서는 리스트가 더 빠를 수 있다.
- 성능 비교 예시
import time
def list_approach(n):
return [i**2 for i in range(n)]
def generator_approach(n):
return (i**2 for i in range(n))
n = 10**6
# 리스트 방식
start = time.time()
squares_list = list_approach(n)
sum(squares_list)
print(f"List time: {time.time() - start}")
# 제너레이터 방식
start = time.time()
squares_gen = generator_approach(n)
sum(squares_gen)
print(f"Generator time: {time.time() - start}")
이 예시는 큰 수의 제곱을 계산하고 합산하는 데 있어 리스트와 제너레이터의 성능을 비교한다. 대부분의 경우 제너레이터가 더 빠르고 메모리 효율적이다.
제너레이터의 디버깅과 테스팅
제너레이터는 지연 평가(lazy evaluation) 특성 때문에 디버깅이 까다로울 수 있다. 이 때 몇 가지 유용한 팁들이다.
1. 로깅 사용
yield 문 주변에 로그를 추가하여 실행 흐름을 추적한다.
2. 리스트로 변환
디버깅 중에 제너레이터를 리스트로 변환하여 모든 값을 한 번에 확인한다.
3. 단위 테스트 작성
제너레이터 함수의 예상 출력을 검증하는 테스트를 작성한다.
- 테스트 예시
import unittest
def even_numbers(n):
for i in range(n):
if i % 2 == 0:
yield i
class TestEvenNumbers(unittest.TestCase):
def test_even_numbers(self):
result = list(even_numbers(10))
self.assertEqual(result, [0, 2, 4, 6, 8])
if __name__ == '__main__':
unittest.main()
'프로그래밍 > 파이썬' 카테고리의 다른 글
파이썬 - Set (0) | 2024.11.29 |
---|---|
파이썬 - 딕셔너리 (0) | 2024.11.29 |
파이썬 - 컴프리헨션 (0) | 2024.11.27 |
파이썬 - 튜플 개념 정리 (0) | 2024.11.26 |
파이썬 - 리스트 메서드 정리 (0) | 2024.11.25 |