C.W.K.
Stream
Lesson 04 of 07 · published

Generator expression vs list 컴프리헨션 — 언제 어느 쪽

~15 min · generator-expression, comprehension, lazy, memory

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

같은 문법, 반대 의미

유일한 시각적 차이는 괄호. [x*x for x in nums] 는 list 컴프리헨션 — list 만들어 반환. (x*x for x in nums) 는 generator expression — lazy iterator 반환. 성능과 메모리 특성 완전히 달라.

generator expression 이 이기는 경우

세 가지. (1) 값을 한 번만 소비하고 다른 consumer (sum, any, all, max, min, for 루프) 에 먹일 때. (2) source 가 거대하거나 무한일 때. (3) short-circuit 원할 때 — 어떤 조건 만족하자마자 처리 멈추기. 셋 다 list 먼저 만드는 건 낭비.

list 컴프리헨션이 이기는 경우

결과를 두 번 이상 반복할 때, len()/인덱싱/슬라이싱 필요할 때, list 가 필요한 라이브러리 API 에 넘길 때. generator 는 그 어느 것도 지원 X. generator 를 그냥 기본 동작 활성화하려고 list(...) 로 감싸고 있으면 — 처음부터 list 컴프리헨션이 답.

괄호 생략 idiom

generator expression 이 함수 호출의 *유일한* 인자면 바깥 괄호 생략 가능. sum((x*x for x in nums))sum(x*x for x in nums) 와 같음. 가장 흔한 형태 — 대부분 generator expression 은 다른 함수의 괄호 안에 살아.

Pythonic Way: "~ 의 모든 원소 처리" 파이프라인의 디폴트는 generator expression 이어야 해. list 가 *명시적* 으로 필요할 때만 list 컴프리헨션. 대부분 초보가 list 컴프리헨션을 디폴트로 쓰고 10GB 로그 파일 반복하는 날 비용을 배워.

Code

메모리 차이 — 측정 가능·python
import sys

lc = [x*x for x in range(1_000_000)]    # 백만 원소 list
gen = (x*x for x in range(1_000_000))    # generator 객체

print(sys.getsizeof(lc))        # ~8MB
print(sys.getsizeof(gen))       # ~200 바이트 — 작아

# 소비할 때 같은 답
print(sum(lc) == sum((x*x for x in range(1_000_000))))  # True
Short-circuit — generator expression 이 일 아껴·python
def expensive(x):
    print("computing", x)
    return x * x

# list 컴프리헨션 — 모든 원소 *먼저* 계산
result_list = any(v > 10 for v in [expensive(x) for x in [1, 2, 3, 4, 5]])
# 'computing' 5 줄 모두 출력 *후* 체크

print("---")

# generator expression — 매치 찾으면 short-circuit
result_gen = any(expensive(x) > 10 for x in [1, 2, 3, 4, 5])
# 딱 필요한 만큼만 — 첫 hit 에서 멈춤
list 로 가야 할 때 — 다중 소비·python
# 이거 작동 X — generator 는 첫 사용 후 소진
gen = (x*x for x in range(5))
print(list(gen))             # [0, 1, 4, 9, 16]
print(list(gen))             # []   <- 소진
print(sum(gen))              # 0    <- 여전히 소진

# 여러 번 써야 하면 list 컴프리헨션
lst = [x*x for x in range(5)]
print(list(lst))             # [0, 1, 4, 9, 16]
print(list(lst))             # [0, 1, 4, 9, 16]
print(sum(lst))              # 30
괄호 생략 idiom·python
nums = [1, 2, 3, 4, 5]

# 세 가지 다 작동, 세 번째가 가장 흔함
print(sum((x*x for x in nums)))     # 55
print(sum( (x*x for x in nums) ))   # 55
print(sum(x*x for x in nums))       # 55  <- 표준 idiom

# any/all/max/min 똑같이
print(any(x > 3 for x in nums))     # True
print(max(x*2 for x in nums))       # 10

External links

Exercise

100 줄 파일 (또는 멀티라인 문자열로 시뮬레이션) 읽고, generator expression 파이프라인 — (a) 각 줄 공백 strip, (b) 빈 줄 필터, (c) 각 비-빈 줄의 첫 단어 추출, (d) 4 글자 초과 단어만 yield. 마지막 파이프라인을 set() 에 — 유니크한 긴 첫-단어들. 파이프라인 어디서도 100 줄 strip 된 list 를 통째 안 만든 거 확인.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.