한 줄, 한 생각
이 패턴은 이미 여러 번 써보셨을 거예요: 빈 리스트로 시작해서, 뭔가를 반복하고, 필요하면 걸러내고, 결과에 추가하는 거요.
리스트 컴프리헨션은 같은 일을 한 줄로 말해요:
왼쪽에서 오른쪽으로 읽으세요: "numbers의 각 n에 대해 n * 2로 이루어진 새 리스트." 구조는 [표현식 for 아이템 in 이터러블]이에요.
이건 파이썬만의 트릭이 아니라 컴프리헨션(comprehension) 이라는 개념이에요 — 리스트를 어떻게 만들지의 기계적인 절차가 아니라, 새 리스트에 뭐가 들어갈지를 선언적으로 설명하는 거니까요.
필터 붙이기
반복 부분 뒤에 if 절을 붙이면 필터링이 돼요:
두 번째 줄을 읽으면: "numbers의 각 n에 대해 n * n, 단 n이 홀수일 때만."
같은 걸 반복문으로 쓰면:
둘 다 괜찮아요. 컴프리헨션이 더 짧고, 몇 번 읽어보면 한 줄에 의도가 다 담겨 있어서 오히려 훑어보기 쉬워요.
매핑과 필터링을 동시에
값을 변환하면서 동시에 필터링할 수도 있어요:
세 글자보다 긴 각 단어를, 대문자로 바꿔서 담는다.
컴프리헨션 안의 중첩 반복
for를 두 개 쓰면 곱집합 같은 쌍이 만들어져요:
중첩 반복문을 썼을 때와 순서가 똑같아요. 첫 번째 for가 바깥, 두 번째가 안쪽이에요. 들여쓰기된 반복문을 왼쪽에서 오른쪽으로 편 모양처럼 읽히거든요.
두 단계까지가 일반 반복문보다 낫게 읽히는 한계예요. 세 단계가 되면 반복문으로 돌아가세요.
딕셔너리와 집합 컴프리헨션
같은 아이디어, 다른 중괄호:
집합 컴프리헨션과 딕셔너리 컴프리헨션은 첫눈에 똑같아 보여요 — 차이는 key: value냐, 단일 표현식이냐예요. 중괄호 + : = dict, 중괄호에 : 없음 = set.
제너레이터 표현식
리스트 컴프리헨션과 거의 쌍둥이인데, 대괄호 대신 소괄호를 쓰고 — 결정적으로 리스트를 만들지 않아요:
제너레이터를 sum()과 any()에 바로 넘겼다는 데 주목하세요 — 괄호를 따로 더 씌우지 않아요. 호출한 쪽이 한 번만 순회하면 되는 상황에 맞는 도구예요. 큰 컬렉션에서는 전체 리스트보다 메모리에 훨씬 친화적입니다.
일반 반복문을 써야 할 때
컴프리헨션은 매혹적이에요. 기술적으로는 한 줄에 많은 걸 쑤셔 넣을 수 있거든요. 하지만 그러지 마세요.
평범한 반복문을 꺼내 쓰세요, 다음과 같은 경우에:
- 변환이 한 단계 이상일 때. 중간 변수가 필요하면 펼쳐서 쓰세요.
- 복잡한 에러 처리나 분기가 있을 때.
- 읽는 사람이 한 번 이상 멈춰야 이해할 수 있을 때.
제가 쓰는 기준: 한 호흡에 읽어서 뭘 하는지 이해되면 남겨두고, 더듬거리면 반복문으로 고쳐 써요.
어떤 컴프리헨션은 똑똑해 보이지만 아파요:
같은 결과예요. 반복문 버전은 한 줄이 아니라 다섯 줄이지만, "더 길다"는 "더 나쁘다"가 아닙니다.
계속 재활용하게 될 패턴들
이 다섯 가지 패턴이 일상적인 데이터 작업의 꽤 많은 부분을 커버합니다.
다음에는
이제 주요 컬렉션 타입들과, 그것들을 묶어주는 컴프리헨션까지 다 봤어요. 다음 챕터는 함수입니다 — 동작을 이름 있는, 재사용 가능한 단위로 포장하는 것.
자주 묻는 질문
파이썬의 리스트 컴프리헨션이 뭔가요?
기존 이터러블에서 새 리스트를 만드는 간결한 문법이에요. [x * 2 for x in numbers]는 각 숫자를 두 배로 한 새 리스트를 만들어요. 필터링도 가능해요: [x for x in numbers if x > 0].
리스트 컴프리헨션을 쓰지 말아야 할 때는 언제인가요?
가독성을 해칠 때요. 안쪽 표현식이 복잡하거나 깊게 중첩돼 있다면, 적절한 변수 이름을 붙인 일반 for 반복문이 더 명확해요. 컴프리헨션은 단순한 변환 — 매핑과 필터링 — 용이에요. 그 이상으로 복잡해지면 전체 반복문으로 쓰는 편이 낫습니다.
리스트 컴프리헨션과 제너레이터 표현식은 뭐가 다른가요?
리스트 컴프리헨션은 리스트 전체를 메모리에 만들어요. 제너레이터 표현식은(문법은 같고 괄호만 대괄호가 아닌 소괄호) 한 번에 하나씩 값을 내놓아요. 결과를 한 번만 순회해서 쓸 대상(예: sum(...)) 에 넘길 때는 제너레이터를 쓰세요 — 곧 버릴 리스트를 굳이 만들 필요가 없어요.