질문 배경
파이썬은 2.4 버전부터 리스트 표현식(list comprehensions)에 "생성기 표현식"(generator expressions)을 추가했습니다. 이는 값의 지연 시퀀스를 생성할 수 있게 해주며, 생성기와 비슷하지만 간결하고 가독성이 좋은 형태입니다.
문제
리스트 표현식([x for x in iterable])은 모든 요소를 메모리에 즉시 로드하여 리스트를 생성합니다. 이는 요소의 수가 매우 많을 경우 비효율적이거나 심지어 위험할 수 있습니다. 생성기 함수(yield를 사용하는)는 더 유연하지만, 별도의 함수 정의와 더 많은 코드 줄을 요구합니다.
해결책
생성기 표현식((x for x in iterable))은 여유 있는 문법을 제공하여 지연된 시퀀스를 생성합니다(요소는 필요 시 계산되며, 모두 한 번에 로드되지 않습니다). 이는 리스트 표현식과 비슷하게 보이지만, 소괄호를 사용합니다:
# 리스트 표현식은 모든 것을 메모리에 로드함 squares_list = [x**2 for x in range(10**6)] # 생성기 표현식: 요소는 요청 시 제공되며, 메모리를 거의 사용하지 않음 squares_gen = (x**2 for x in range(10**6)) # 생성기의 처음 5개 값을 가져옴 for _ in range(5): print(next(squares_gen))
주요 특징:
같은 생성기 표현식을 여러 번 반복할 수 있나요?
아니요, 한 번 반복한 후 생성기는 "소진"됩니다. 다시 반복하려면 새로운 생성기를 만들거나 리스트 표현식을 사용해야 합니다.
it = (x for x in range(3)) print(list(it)) # [0,1,2] print(list(it)) # [] — 더 이상 값을 얻을 수 없음
생성기는 사용 간 상태를 유지하나요?
네, 생성기 표현식은 next() 호출 간에 "위치"를 유지하지만, 새 객체를 만들지 않는 한 처음으로 재설정할 수는 없습니다.
한 줄에서 생성기 표현식을 여러 번 사용할 수 있나요?
아니요! 생성기를 한 번에 여러 곳에 "언팩"하면(예: 여러 함수에 동시에 전달하고 리스트로 반환하지 않으면), 일부 데이터가 손실됩니다 — 각 자식 사용은 포인터를 앞으로 이동시킵니다.
g = (x for x in range(3)) print(sum(g), list(g)) # sum(g)는 모두 얻지만, list(g)는 비게 됨
대용량 파일을 분석하는 프로젝트에서 사용됨:
data = (parse_line(line) for line in file) process(list(data)) other_process(list(data))
장점:
단점:
데이터의 재사용이 필요할 때 리스트 컴프리헨션을 사용하거나, 일회용 소비를 위해 생성기를 생성함:
# 분석을 위한 일회용 생성기(예: 합계 계산) total = sum(parse_line(line) for line in file)
장점:
단점: