문제의 역사
게으른 평가(Lazy Evaluation)는 계산이 코드에서 결과가 필요할 때까지 연기되는 프로그래밍 기술입니다. 파이썬에서는 이 패러다임이 제너레이터와 itertools와 같은 표준 라이브러리의 특수 함수 덕분에 인기를 얻었습니다. 이러한 기술은 본래 함수형 언어에서 유래했지만, 파이썬은 게으른 처리를 위한 본토 및 서드파티 도구를 제공합니다.
문제
전통적인 즉시 평가(Eager Evaluation)는 모든 데이터를 한 번에 로드하고 계산해야 하므로(예: 리스트 표현식 사용 시) 메모리 소모가 크고 대규모 또는 무한 시퀀스를 처리할 때 성능 저하를 초래할 수 있습니다. 게으른 처리는 필요할 때만 요소를 '로드'하고 처리할 수 있게 하여 불필요한 자원 소모를 피합니다.
해결책
파이썬에서 게으른 처리는 제너레이터(yield)뿐 아니라 itertools 모듈의 특별한 게으른 함수, map, filter와 같은 표준 함수, 그리고 제너레이터 표현식(generator expressions) 유형의 객체를 통해 구현됩니다. 예를 들어, map() 함수는 호출됨에 따라 값을 계산하는 게으른 이터레이터를 반환합니다:
# 게으른 처리 예: 각 수를 제곱 squares = map(lambda x: x ** 2, range(10**10)) # 리스트에 메모리 소모 없음 print(next(squares)) # 0 print(next(squares)) # 1
주요 특징:
파이썬에서 map() 함수는 항상 게으른 처리를 하나요? 어떤 표준 함수가 게으른지 어떻게 알 수 있나요?
아니요, 파이썬 3부터 map(), filter(), zip() 함수는 이터레이터를 반환하므로 게으른 처리를 구현합니다. 파이썬 2에서는 이러한 함수들이 리스트를 반환했습니다. 객체가 게으른지 확인하려면 그 유형을 확인하거나 문서를 참조해야 합니다:
result = map(lambda x: x+1, range(5)) print(type(result)) # 'map' — 이터레이터입니다.
sum() 함수 내부에서 제너레이터 표현식을 사용해 게으른 처리가 가능할까요?
sum() 함수는 이터레이터의 끝까지 진행해야 합니다. 제너레이터 표현식 자체는 게으르지만, sum()은 결국 전체 시퀀스를 소비합니다:
s = sum(x**2 for x in range(1000000)) # 제너레이터가 완전히 소모됨
일반 리스트와 튜플에 대해 map/lambda를 통해 게으른 처리를 적용할 수 있나요?
네, 가능하지만 리스트와 튜플은 여전히 메모리에 로드됩니다. map은 이들에 대해 게으른 이터레이터를 반환하지만 원래 데이터는 여전히 메모리에 모두 존재합니다. 완전한 게으른 체인을 얻으려면 각 단계에서 제너레이터를 사용하는 것이 좋습니다:
def gen(): for i in range(1, 100): yield i squares = map(lambda x: x**2, gen()) # 모두 게으름
개발자가 필터링을 위해 제너레이터 표현식을 사용하면서 큰 로그 파일을 처리하는 코드를 작성하지만, 실수로 그것을 바로 리스트로 변환합니다:
with open('biglog.txt') as f: important_lines = [line for line in f if 'ERROR' in line] # 전체 파일을 로드함
장점:
단점:
다른 팀은 제너레이터 표현식을 사용하여 게으른 접근 방식을 취하고, 문자열을 수신하는 대로 처리합니다:
with open('biglog.txt') as f: for line in (l for l in f if 'ERROR' in l): process(line)
장점:
단점: