python comprehension, list comprehension, dict comprehension, set comprehension, generator expression
정의
**Comprehension(컴프리헨션)**은 iterable로부터 list/set/dict/generator를 선언적·간결하게 만드는 문법이다. 일반 for+append 보다 30-50% 빠르며 (전용 바이트코드 LIST_APPEND 등), Pythonic 코드의 핵심 관용구다.
4가지 형태
[expr for x in iter if cond] # list{expr for x in iter if cond} # set{k: v for x in iter if cond} # dict(expr for x in iter if cond) # generator (괄호: lazy)
list comprehension
python
squares = [x ** 2 for x in range(5)]print(squares)evens = [x for x in range(10) if x % 2 == 0]print(evens)# 변환 + 필터 동시upper_long = [s.upper() for s in ["hi", "world", "py"] if len(s) > 2]print(upper_long)# 중첩 forpairs = [(x, y) for x in range(2) for y in range(2)]print(pairs)# 2차원 → 1차원 (flatten)matrix = [[1, 2], [3, 4], [5, 6]]flat = [x for row in matrix for x in row]print(flat)
[(x, y) for x in A for y in B]# 동등한 일반 forresult = []for x in A: for y in B: result.append((x, y))
왼쪽 for가 바깥 루프. 헷갈리면 일반 for로 바꿔서 확인.
조건부 표현식
if는 필터, if-else는 표현식 변환.
# 필터 (수만 통과)[x for x in nums if x > 0]# 변환 (모두 통과, 값만 바뀜)[x if x > 0 else 0 for x in nums]
dict comprehension
python
squares = {x: x ** 2 for x in range(5)}print(squares)# dict 뒤집기d = {"a": 1, "b": 2}inverted = {v: k for k, v in d.items()}print(inverted)# 필터링filtered = {k: v for k, v in d.items() if v > 1}print(filtered)
unique_lengths = {len(s) for s in ["a", "bb", "cc", "ddd"]}print(unique_lengths)vowels = {ch for ch in "comprehensions" if ch in "aeiou"}print(vowels)
결과
{1, 2, 3}{'e', 'i', 'o'}
generator expression
**괄호()**로 감싸면 generator를 반환한다. 메모리 효율적인 lazy 시퀀스.
python
# 차이: list (즉시 생성) vs gen (lazy)import syslst = [x ** 2 for x in range(1000)]gen = (x ** 2 for x in range(1000))print(sys.getsizeof(lst), "bytes (list)")print(sys.getsizeof(gen), "bytes (gen)")# 함수 단일 인자일 때 괄호 생략 가능total = sum(x ** 2 for x in range(100))print(total)
결과
8056 bytes (list)208 bytes (gen)328350
거대한 데이터를 처리할 때는 항상 generator 먼저 고려. list 컴프리헨션을 메모리에 들고 있을 이유가 없으면 괄호로 바꿔라.
성능: list comp vs map/filter
# list comp[x ** 2 for x in xs if x > 0]# map + filter (역사적 대안)list(map(lambda x: x ** 2, filter(lambda x: x > 0, xs)))
CPython에서 lambda 호출 비용이 크기 때문에 list comp가 거의 항상 빠르다. map(operator.add, ...)처럼 lambda 없이 내장 함수만 쓰면 map이 비등하거나 더 빠를 수 있다.
Walrus(:=)와 결합
같은 비싼 계산을 필터와 표현식에서 두 번 하지 않게.
# WRONG: f(x)를 두 번 호출results = [f(x) for x in xs if f(x) is not None]# 3.8+results = [y for x in xs if (y := f(x)) is not None]
async comprehension (3.6+)
async def fetch_all(): return [await fetch(url) async for url in urls()]# async generatorasync def squares(): async for n in numbers(): yield n ** 2
async 컨텍스트(코루틴 함수 내부)에서만 가능.
가독성 한계
너무 복잡하면 일반 for로 바꿔라. 일반적 규칙:
중첩 3단계 이상 → for로 분해
조건 2개 이상 + 변환 복잡 → 함수로 추출
한 줄 80자 초과 → 부분식 변수로 추출 또는 for
# 나쁨: 한 줄에 너무 많음result = [transform(x) for sublist in matrix for x in sublist if x > 0 and not is_blacklisted(x) and ...]# 좋음def is_valid(x): return x > 0 and not is_blacklisted(x)result = [transform(x) for sublist in matrix for x in sublist if is_valid(x)]
스코프 (3.x)
Python 3 컴프리헨션은 자체 스코프를 가진다. 변수 누출 없음.
x = 99squares = [x ** 2 for x in range(5)]print(x) # 99 (덮어쓰지 않음)
Python 2에서는 누출됐지만 3에서 수정됨.
함정: 비싼 호출 반복
# WRONG: 매 반복마다 expensive_call() 호출[expensive_call(x) for x in xs if expensive_call(x).is_ok]# OK[y for x in xs if (y := expensive_call(x)).is_ok]
💬 댓글