13. 컴프리헨션

🎯 이 장의 목표
  • 리스트 컴프리헨션으로 반복+생성을 한 줄에 쓴다.
  • 조건 필터와 변환을 컴프리헨션에 녹인다.
  • 딕셔너리·세트 컴프리헨션을 활용한다.
  • zip·enumerate와 제너레이터 표현식을 안다.

먼저: 컴프리헨션이 뭔가요?

초급편 8장에서 "리스트 만들기" 패턴을 봤습니다. 빈 리스트를 만들고 반복하며 append하는 방식이죠.

PYTHON
squares = []
for n in range(1, 6):
    squares.append(n ** 2)
print(squares)      # [1, 4, 9, 16, 25]

이 패턴은 너무 흔해서 Python은 이를 한 줄로 쓰는 문법을 제공합니다. 바로 컴프리헨션(comprehension)입니다. "이해·내포"라는 뜻인데, 수학의 집합 표기 { n² | n ∈ 1..5 }를 코드로 옮긴 것이라 생각하면 됩니다.

PYTHON
squares = [n ** 2 for n in range(1, 6)]
print(squares)      # [1, 4, 9, 16, 25]   ← 위와 똑같은 결과, 한 줄!

위 두 코드는 완전히 같은 일을 합니다. 컴프리헨션이 더 짧고, 익숙해지면 더 읽기 좋습니다.

flowchart LR
    Src["range(1,6)<br/>1,2,3,4,5"]:::data --> Map["각 n에<br/>n**2 적용"]:::proc
    Map --> Out["[1,4,9,16,25]"]:::result

    classDef data fill:#a8dadc,stroke:#457b9d,color:#1d3557
    classDef proc fill:#7fd8d8,stroke:#2a9d8f,color:#14532d
    classDef result fill:#b8e6c1,stroke:#34a853,color:#14532d
📌 핵심
핵심: 리스트 컴프리헨션의 기본 형태는 [식 for 변수 in 반복가능]이다. "반복가능한 것의 각 항목에 식을 적용해 새 리스트를 만든다".

구조 뜯어보기

TEXT
[ n ** 2   for n in range(1, 6) ]
  ──┬──     ────────┬──────────
   식        반복 (for ~ in ~)
 (각 항목을      (8장의 for와
  무엇으로        똑같은 모양)
  바꿀지)

읽는 순서는 "range(1,6)의 각 n에 대해, n ** 2를 모아 리스트로"입니다. 오른쪽 for 부분을 먼저 읽고, 왼쪽 식을 나중에 읽으면 자연스럽습니다.

루프와 컴프리헨션 1:1로 맞춰 보기

처음엔 "어느 부분이 어디서 왔는지" 헷갈립니다. for 루프와 컴프리헨션을 같은 색끼리 맞춰 보면 분명해집니다.

PYTHON
# 일반 for 루프
result = []                        # ① 빈 리스트
for n in range(1, 6):              # ② 반복
    result.append(n ** 2)          # ③ 변환해서 추가

# 컴프리헨션 — 위 세 줄을 한 줄로
result = [n ** 2 for n in range(1, 6)]
#          └──③──┘ └─────②──────┘
#         (추가할 식)   (반복 부분)
#         ①(리스트로 모으는 건 대괄호가 담당)

대응 관계를 표로 보면 이렇습니다.

일반 루프컴프리헨션 위치역할
result = []바깥 [ ]결과를 리스트로 모음
for n in range(1,6):가운데 for n in range(1,6)반복 (그대로!)
result.append(n**2)맨 앞 n ** 2각 항목을 무엇으로 바꿀지
💡 팁
컴프리헨션을 쓸 때는 "먼저 for부터 적고, 그다음 맨 앞 식을 채운다"는 순서로 생각하면 쉽습니다. "무엇을 반복하지?(for n in ...) → 각각을 어떻게 바꾸지?(n ** 2)" 순입니다.

문자열도 반복 가능하니 이렇게도 됩니다.

PYTHON
chars = [c.upper() for c in "python"]
print(chars)        # ['P', 'Y', 'T', 'H', 'O', 'N']

조건 필터: if 붙이기

뒤에 if 조건을 붙이면, 조건을 만족하는 항목만 골라 담습니다.

PYTHON
evens = [n for n in range(10) if n % 2 == 0]
print(evens)        # [0, 2, 4, 6, 8]

변환과 필터를 함께 쓸 수도 있습니다.

PYTHON
words = ["hi", "Python", "a", "world"]
result = [w.upper() for w in words if len(w) > 2]
print(result)       # ['PYTHON', 'WORLD']   (3글자 이상만 골라 대문자로)

읽는 법: "words의 각 w 중 길이가 2 초과인 것만 골라, 대문자로 바꿔 모아라".

📌 핵심
핵심: 뒤에 붙는 if필터다. [식 for 변수 in 반복 if 조건] = "조건을 통과한 항목만 식을 적용".

변환 시 분기: 삼항 표현식

"걸러내기"가 아니라 "값에 따라 다르게 변환"하려면 (초급편 7장의) 삼항 표현식을 식 자리에 씁니다.

PYTHON
labels = ["짝" if n % 2 == 0 else "홀" for n in range(5)]
print(labels)       # ['짝', '홀', '짝', '홀', '짝']
⚠️ 흔한 실수
흔한 함정 — if의 두 위치: if가 어디 있느냐로 의미가 완전히 다릅니다.
  • [n for n in range(5) if n % 2 == 0]필터(뒤의 if): 짝수만 남김 → [0, 2, 4]
  • ["짝" if n%2==0 else "홀" for n in range(5)]변환 분기(앞의 삼항): 전부 남기되 라벨 변경

"거르려면 뒤에 if, 값을 바꾸려면 앞에 if...else"로 기억하세요.

딕셔너리·세트 컴프리헨션

대괄호 대신 중괄호를 쓰면 딕셔너리나 세트가 됩니다.

딕셔너리 컴프리헨션

{키: 값 for ...} 형태입니다.

PYTHON
sq_dict = {n: n ** 2 for n in range(1, 4)}
print(sq_dict)      # {1: 1, 2: 4, 3: 9}

prices = {"사과": 1000, "배": 3000, "감": 500}
# 값을 10% 인상한 새 딕셔너리
raised = {name: price * 1.1 for name, price in prices.items()}
print(raised)       # {'사과': 1100.0..., '배': 3300.0..., '감': 550.0...}

세트 컴프리헨션

{값 for ...} 형태(콜론 없음). 중복이 자동으로 제거됩니다.

PYTHON
dup = [1, 2, 2, 3, 3, 3]
unique_squares = {n ** 2 for n in dup}
print(sorted(unique_squares))   # [1, 4, 9]   (중복 제거됨)
컴프리헨션기호형태결과
리스트[ ][식 for ...]list
딕셔너리{ }{키: 값 for ...}dict
세트{ }{식 for ...}set

zip: 두 리스트를 짝짓기

zip()은 여러 시퀀스를 같은 위치끼리 묶어줍니다. 지퍼가 양쪽 이를 맞물리는 모습을 떠올리세요.

PYTHON
names = ["민지", "현우", "수빈"]
ages = [25, 30, 28]

for name, age in zip(names, ages):
    print(f"{name}: {age}살")
# 민지: 25살
# 현우: 30살
# 수빈: 28살

컴프리헨션과 결합하면 두 리스트로 딕셔너리를 만드는 게 한 줄입니다.

PYTHON
people = {name: age for name, age in zip(names, ages)}
print(people)       # {'민지': 25, '현우': 30, '수빈': 28}
💡 팁
zip은 가장 짧은 시퀀스 길이에 맞춰 멈춥니다. 길이가 다르면 짧은 쪽 기준으로 잘립니다.

enumerate: 번호와 함께

초급편 8장에서 본 enumerate()도 컴프리헨션에서 유용합니다. 번호가 필요할 때 씁니다.

PYTHON
names = ["민지", "현우"]
numbered = [f"{i+1}. {name}" for i, name in enumerate(names)]
print(numbered)     # ['1. 민지', '2. 현우']

💡 제너레이터 표현식: 메모리를 아끼는 사촌

대괄호 대신 소괄호를 쓰면 제너레이터 표현식이 됩니다. 리스트는 모든 값을 즉시 만들어 메모리에 담지만, 제너레이터는 필요할 때 하나씩 만들어내 메모리를 아낍니다.

PYTHON
# 리스트: 100만 개를 전부 메모리에 만듦
squares_list = [n ** 2 for n in range(1_000_000)]

# 제너레이터: 값을 미리 만들지 않고, 요청할 때 하나씩
squares_gen = (n ** 2 for n in range(1_000_000))
print(type(squares_gen))    # <class 'generator'>

sum()처럼 한 번 훑고 버리면 되는 경우엔 제너레이터 표현식이 효율적입니다. 대괄호 없이 함수에 바로 넣을 수 있습니다.

PYTHON
total = sum(n ** 2 for n in range(5))   # 리스트를 안 만들고 합산
print(total)        # 30
📎 제너레이터의 동작 원리(yield, 지연 평가)는 다음 14장에서 자세히 다룹니다. 지금은 "큰 데이터를 한 번만 훑을 땐 ( ) 제너레이터가 메모리에 좋다" 정도면 충분합니다.

나쁜 예 ❌ vs 좋은 예 ✅

PYTHON
nums = [1, 2, 3, 4, 5, 6]

# ❌ 나쁜 예: 단순 변환에 장황한 루프
result = []
for n in nums:
    if n % 2 == 0:
        result.append(n * 10)

# ✅ 좋은 예: 컴프리헨션 한 줄
result = [n * 10 for n in nums if n % 2 == 0]
print(result)       # [20, 40, 60]
⚠️ 흔한 실수
단, 과용 금지: 컴프리헨션이 길고 복잡해지면(중첩 2단계 이상, 조건 여럿) 오히려 읽기 어렵습니다. 그럴 땐 일반 for 루프가 낫습니다. "한눈에 읽히는가"가 기준입니다.

이 장에서 배운 것

  • 리스트 컴프리헨션 [식 for 변수 in 반복]은 "반복하며 새 리스트 만들기"를 한 줄로 쓴다.
  • 뒤의 if 조건필터, 식 자리의 삼항 A if 조건 else B변환 분기다. 위치로 의미가 갈린다.
  • 중괄호로 딕셔너리({키:값 for}세트({식 for}) 컴프리헨션을 만든다.
  • zip으로 여러 시퀀스를 짝짓고, enumerate로 번호를 얻는다.
  • 소괄호 제너레이터 표현식 (식 for ...)은 값을 하나씩 만들어 메모리를 아낀다.
  • 복잡해지면 컴프리헨션보다 일반 루프가 낫다 — 가독성이 기준이다.

🧪 실습 문제

문제 1. 컴프리헨션으로 1부터 10까지 각 수의 세제곱 리스트를 만들어 출력하세요. (결과: [1, 8, 27, ...])

문제 2. 리스트 nums = [15, 22, 8, 41, 6, 30]에서 20 이상인 수만 골라 새 리스트를 컴프리헨션으로 만드세요.

문제 3. 다음 두 컴프리헨션의 결과 차이를 설명하고 각각의 출력을 적으세요.

PYTHON
a = [n for n in range(6) if n % 2 == 0]
b = ["E" if n % 2 == 0 else "O" for n in range(6)]

문제 4. 단어 리스트 words = ["apple", "kiwi", "banana", "fig"]로, {단어: 글자수} 딕셔너리를 컴프리헨션으로 만드세요.

문제 5. 두 리스트 subjects = ["국어", "수학", "영어"]scores = [90, 85, 95]zip과 컴프리헨션으로 묶어 {"국어": 90, ...} 딕셔너리를 만드세요.

<details>

<summary>✅ 정답·해설 보기</summary>

1.

PYTHON
cubes = [n ** 3 for n in range(1, 11)]
print(cubes)   # [1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

2.

PYTHON
nums = [15, 22, 8, 41, 6, 30]
big = [n for n in nums if n >= 20]
print(big)     # [22, 41, 30]

3.

  • a: 뒤의 if는 필터라 짝수만 남김 → [0, 2, 4]
  • b: 앞의 삼항은 전부 남기되 라벨 변환 → ['E', 'O', 'E', 'O', 'E', 'O']

4.

PYTHON
words = ["apple", "kiwi", "banana", "fig"]
lengths = {w: len(w) for w in words}
print(lengths)   # {'apple': 5, 'kiwi': 4, 'banana': 6, 'fig': 3}

5.

PYTHON
subjects = ["국어", "수학", "영어"]
scores = [90, 85, 95]
report = {s: sc for s, sc in zip(subjects, scores)}
print(report)    # {'국어': 90, '수학': 85, '영어': 95}

</details>

◀️ 이전 장: 12. 상속과 매직 메서드 | ▶️ 다음 장: 14. 이터레이터와 제너레이터