Python 제너레이터 표현식
요약
Python의 제너레이터 표현식과 sum/any/all/join 활용법, next() 동작과 게으른 평가까지 정리합니다. 정보처리기사 실기에 자주 등장하는 한 줄 표현식 문법입니다.
제너레이터 표현식 핵심 정리
| 개념 | 형식 | 예시 |
|---|---|---|
| 제너레이터 표현식 | (식 for 변수 in 반복) | (x * 2 for x in lst) |
| 조건부 제너레이터 | (식 for 변수 in 반복 if 조건) | (x for x in lst if x > 0) |
| next로 값 꺼내기 | next(gen) | 호출 시점에 표현식 1회 평가 |
| 게으른 평가 | 요청 시점에 계산 | 큰 데이터 일부만 쓸 때 유리 |
| sum/any/all과 결합 | sum(식 for 변수 in 반복) | sum(x for x in lst) |
제너레이터 표현식 기초
제너레이터 표현식(generator expression) 은 리스트 컴프리헨션의 대괄호 []를 소괄호 ()로 바꾼 형태입니다.
제너레이터 표현식의 형식
리스트 컴프리헨션과 모양이 거의 같지만, 결과로 만들어지는 것이 리스트가 아니라 제너레이터 객체라는 점이 다릅니다.
리스트 컴프리헨션과의 차이
| 항목 | 리스트 컴프리헨션 [...] | 제너레이터 표현식 (...) |
|---|---|---|
| 결과 자료형 | list | generator |
| 메모리 사용 | 모든 결과를 한꺼번에 저장 | 꺼낼 때마다 하나씩 계산 |
| 직접 출력 | 값이 그대로 보임 | <generator object ...>로 보임 |
for문으로 순회 | 가능 (여러 번 가능) | 가능 (한 번 소비 후 끝) |
sum(), any(), all() 인자 | 가능 | 가능 (괄호 생략 가능) |
리스트는 결과 5개를 메모리에 한꺼번에 만들어 보관하지만, 제너레이터는 for문 등으로 하나씩 꺼낼 때마다 그때그때 계산해서 내어줍니다. 그래서 메모리를 적게 쓰고, 첫 번째 값이 빨리 필요한 상황에 유리합니다.
딕셔너리·집합 컴프리헨션까지 포함한 4종 비교는 컴프리헨션 통합 정리 페이지에서 한 자리에 정리합니다.
직접 순회해 보기
제너레이터 객체는 for문이나 next() 함수로 값을 꺼낼 수 있습니다.
next 함수로 값 꺼내기
next()는 제너레이터에서 다음 값을 하나 꺼내는 내장 함수입니다. 한 번 호출할 때마다 표현식을 한 번 평가해 그 결과를 돌려줍니다.

중요한 두 가지 동작이 있습니다.
- 계산 시점이 호출 시점과 같습니다. 제너레이터를 만드는 줄에서는 아무것도 계산되지 않고,
next()를 호출하는 그 순간에 비로소 표현식이 평가됩니다. 이 성질이 게으른 평가의 핵심입니다. - 값이 모두 소진된 뒤에
next()를 한 번 더 호출하면StopIteration예외1가 발생합니다. 위 예시는 값을 3번 꺼냈으므로 한 번 더 호출하면 다음과 같이 됩니다.
실제로 실행하면 다음과 같은 오류 메시지가 출력됩니다.
for문과 next()의 관계
for문은 내부적으로 next()를 반복 호출하다가 StopIteration 예외가 발생하면 자동으로 반복을 멈춥니다. 그래서 다음 두 코드는 동일하게 동작합니다.
for문을 쓰면 StopIteration 처리를 직접 신경 쓸 필요가 없어 편하지만, "값이 한 번에 하나씩 만들어지는 동작"은 next()를 직접 써 봤을 때 가장 잘 보입니다.
게으른 평가 호기심
제너레이터의 가장 큰 특징은 게으른 평가(lazy evaluation)입니다. 값을 미리 다 만들어 두지 않고, 실제로 요청받는 시점에 표현식을 평가하고 결과를 반환합니다. 리스트 컴프리헨션은 반대로 만드는 즉시 모든 결과를 계산해서 메모리에 보관하는 즉시 평가(eager evaluation)입니다.

평가 시점 비교
표현식 안에서 print를 호출해 보면 평가 시점이 한눈에 드러납니다.
코드에서 f"square({x}) 호출됨"은 f-string입니다. 문자열 앞에 f를 붙이고 {} 안에 변수를 넣으면 실행 시점에 값이 끼워 넣어집니다.
리스트는 만드는 순간 square(1), square(2), square(3)이 모두 호출됩니다. 제너레이터는 만드는 줄에서는 아무 호출도 일어나지 않고, next()로 값을 요청할 때 비로소 square(1)이 호출됩니다.
메모리 효율성
게으른 평가의 가장 큰 실용적 장점은 메모리 효율성입니다. 실무에서는 1억 개처럼 매우 많은 값을 다룰 때, 그 중 일부만 필요한 경우가 많습니다. 리스트라면 필요 없는 나머지까지 모두 메모리에 올려야 하지만, 제너레이터는 필요한 만큼만 만들어 씁니다.
Python에서는 큰 숫자를 100_000_000처럼 언더바로 끊어 쓸 수 있어 가독성을 높입니다. 100_000_000은 100000000(1억)과 완전히 같습니다.
코드에서 _는 "값을 받았지만 쓰지 않는다"는 뜻의 관행적 변수명입니다. for _ in range(10)은 "10번 반복하되 반복 변수는 필요 없다"는 의미입니다.
리스트 방식은 1억 개의 결과를 모두 만들어 두고 그중 10개만 사용합니다. 제너레이터 방식은 next()를 10번만 호출했으므로 x * 2도 10번만 평가됩니다. 나머지 99,999,990개는 계산조차 안 됩니다.
앞서 본 표(L67)는 자료형·출력·순회 가능 여부를 비교했고, 여기서는 평가 시점과 메모리 관점으로 다시 정리합니다.
| 항목 | 리스트 컴프리헨션 | 제너레이터 표현식 |
|---|---|---|
| 평가 시점 | 만드는 즉시 (eager) | 요청 시점마다 (lazy) |
| 메모리 사용량 | 결과 전체 크기 | 한 번에 1개 |
| 큰 데이터에서 일부만 쓸 때 | 비효율 | 효율 |
| 같은 값을 여러 번 쓸 때 | 효율 (재사용) | 매번 새로 생성 필요 |
sum, any, all과 함께 쓰기 심화
제너레이터 표현식이 가장 자주 쓰이는 곳은 sum(), any(), all() 같은 집계 함수의 인자입니다. 이런 함수들은 이터러블2을 받아 한 번씩 순회하면서 값을 모으는 동작을 하므로, 제너레이터와 잘 맞습니다.

sum과 결합
sum(이터러블)은 이터러블의 모든 값을 더해 결과를 돌려줍니다.
함수 호출의 괄호 안에 제너레이터 표현식이 들어갈 때는 제너레이터 표현식의 괄호를 생략할 수 있습니다. sum(x * 2 for x in nums)는 sum((x * 2 for x in nums))와 같습니다.
join과 결합 — 조건에 맞는 값만 문자열로 합치기
'구분자'.join(이터러블)은 리스트나 문자열 같은 이터러블의 요소들을 구분자로 이어 붙여 하나의 문자열을 만듭니다. join()의 인자 자리에도 제너레이터 표현식을 바로 넣을 수 있습니다.
숫자 리스트를 문자열로 합칠 때도 str()로 변환하는 제너레이터 표현식을 바로 넘기면 깔끔합니다.
| 패턴 | 의미 | 결과 |
|---|---|---|
''.join(c for c in y) | 모든 문자를 그대로 이어 붙임 | "Hello" |
''.join(c for c in y if 조건) | 조건에 맞는 문자만 이어 붙임 | 조건 만족 문자 집합 |
','.join(str(n) for n in nums) | 숫자 리스트를 문자열로 연결 | "1,2,3" |
조건부 제너레이터 표현식
리스트 컴프리헨션과 마찬가지로, 제너레이터 표현식에도 if 조건을 붙여 원하는 값만 골라낼 수 있습니다.

조건을 통과한 값은 제너레이터가 하나씩 내어 보냅니다(yield). yield는 "값을 하나 꺼내 준다"는 뜻으로, next()를 호출할 때마다 다음 조건 통과 값을 하나씩 전달합니다.
이 표현식을 일반 for문으로 풀어 쓰면 다음과 같습니다.
재귀 함수와 결합한 시험 패턴
정보처리기사 실기에서는 재귀 함수와 제너레이터 표현식이 함께 등장하는 패턴이 출제된 적이 있습니다. 트리 구조의 자식 노드들을 모두 더할 때 다음과 같은 형태로 자주 쓰입니다.

이는 다음 일반 for문과 같습니다.
sum() 안에 제너레이터 표현식을 넣어 한 줄로 줄여 쓴 형태입니다. 한 줄짜리 식이지만 동작은 위 일반 for문과 똑같다는 점을 외워두면 시험에서 분해하기 쉽습니다.
any와 all
any()와 all()도 같은 패턴으로 쓸 수 있습니다. 이 두 함수는 단락 평가(short-circuit)를 합니다. any()는 True인 값을 하나 찾는 순간, all()은 False인 값을 하나 찾는 순간 즉시 순회를 멈춥니다. 나머지 값은 평가하지 않으므로, 제너레이터와 결합하면 불필요한 계산을 줄일 수 있습니다.