파이썬/Basic

next()와 제너레이터 표현식

코샵 2024. 12. 9. 10:30
반응형

소개

Python의 next()와 제너레이터 표현식은 메모리 효율적인 데이터 처리를 위한 강력한 도구입니다. 이번 글에서는 이들의 사용법과 실제 활용 사례를 살펴보겠습니다.

next() 함수 이해하기

기본 사용법

# 간단한 이터레이터 생성
numbers = iter([1, 2, 3])

# next() 사용
first = next(numbers)  # 1
second = next(numbers)  # 2
third = next(numbers)  # 3

# StopIteration 예외 발생
try:
    next(numbers)  # StopIteration 발생
except StopIteration:
    print("더 이상 요소가 없습니다")

next()의 기본값 활용

numbers = iter([1, 2, 3])

# 기본값 설정
value = next(numbers, -1)  # 1
value = next(numbers, -1)  # 2
value = next(numbers, -1)  # 3
value = next(numbers, -1)  # -1 (기본값 반환)
장점 단점
메모리 효율적 이전 값 재접근 불가
지연 평가 지원 인덱스 접근 불가
기본값 설정 가능 길이 계산 어려움
직관적인 사용법 반복 후 재사용 불가

제너레이터 표현식

기본 문법

# 리스트 컴프리헨션
squares_list = [x**2 for x in range(1000000)]  # 메모리에 모든 값 저장

# 제너레이터 표현식
squares_gen = (x**2 for x in range(1000000))  # 필요할 때만 값 생성

활용

# 파일 읽기
def read_large_file(file_path):
    with open(file_path) as f:
        return (line.strip() for line in f)

# 데이터 필터링
numbers = (x for x in range(1000) if x % 2 == 0)

# 데이터 변환
names = ['Alice', 'Bob', 'Charlie']
upper_names = (name.upper() for name in names)

실전 활용

대용량 데이터 처리

def process_large_dataset(data_path):
    # 파일에서 데이터 읽기
    lines = (line.strip() for line in open(data_path))

    # 데이터 필터링 및 변환
    processed = (
        int(line) 
        for line in lines 
        if line.isdigit()
    )

    # 청크 단위 처리
    chunk = []
    chunk_size = 1000

    for num in processed:
        chunk.append(num)
        if len(chunk) == chunk_size:
            process_chunk(chunk)
            chunk = []

메모리 효율적인 데이터 변환

class DataTransformer:
    def __init__(self, data):
        self.data = data

    def transform(self):
        # 단계별 변환 파이프라인
        return (
            self._process(item)
            for item in self.data
            if self._validate(item)
        )

    def _validate(self, item):
        return bool(item)

    def _process(self, item):
        return item.upper()

# 사용 예시
transformer = DataTransformer(['a', 'b', 'c'])
result = transformer.transform()
first = next(result)  # 'A'

무한 시퀀스 생성

def infinite_sequence():
    num = 0
    while True:
        yield num
        num += 1

# 피보나치 수열
def fibonacci_generator():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# 사용 예시
numbers = infinite_sequence()
first_five = [next(numbers) for _ in range(5)]  # [0, 1, 2, 3, 4]

데이터 파이프라인 구축

def data_pipeline():
    # 데이터 소스
    raw_data = (x for x in range(1000))

    # 필터링
    filtered = (
        x for x in raw_data
        if x % 2 == 0
    )

    # 변환
    transformed = (
        x * 2 for x in filtered
    )

    # 그룹화
    groups = (
        (i, list(group))
        for i, group in itertools.groupby(transformed, key=lambda x: x // 10)
    )

    return groups

커스텀 이터레이터

class DataStream:
    def __init__(self, start=0, step=1):
        self.current = start
        self.step = step

    def __iter__(self):
        return self

    def __next__(self):
        value = self.current
        self.current += self.step
        return value

# 사용 예시
stream = DataStream(start=5, step=2)
values = [next(stream) for _ in range(5)]  # [5, 7, 9, 11, 13]

성능 최적화 팁

메모리 사용량 비교

import sys

# 리스트 컴프리헨션
numbers_list = [x for x in range(1000000)]
print(sys.getsizeof(numbers_list))  # 큰 메모리 사용

# 제너레이터 표현식
numbers_gen = (x for x in range(1000000))
print(sys.getsizeof(numbers_gen))  # 작은 메모리 사용

실행 시간 최적화

from itertools import islice

def optimize_processing():
    # 전체 데이터를 한 번에 처리하는 대신
    # 청크 단위로 처리
    data = (x for x in range(1000000))
    chunk_size = 1000

    while True:
        chunk = list(islice(data, chunk_size))
        if not chunk:
            break
        process_chunk(chunk)

주의사항과 팁

이터레이터 소진 주의

numbers = (x for x in range(5))
list(numbers)  # [0, 1, 2, 3, 4]
list(numbers)  # [] (이미 소진됨)

# 재사용이 필요한 경우
def get_numbers():
    return (x for x in range(5))

list(get_numbers())  # [0, 1, 2, 3, 4]
list(get_numbers())  # [0, 1, 2, 3, 4]

디버깅 팁

def debug_generator(gen):
    for value in gen:
        print(f"Debug: {value}")
        yield value

# 사용 예시
numbers = (x for x in range(5))
debugged = debug_generator(numbers)
list(debugged)  # 각 값을 출력하면서 처리

마치며

next()와 제너레이터 표현식은 Python에서 메모리 효율적인 프로그래밍을 가능하게 하는 강력한 도구입니다. 특히 대용량 데이터 처리나 스트리밍 데이터를 다룰 때 매우 유용합니다. 상황에 맞게 적절히 활용하면 프로그램의 성능을 크게 향상시킬 수 있습니다.