Python & AI Tutorials Logo
Python 프로그래밍

34. 컴프리헨션(Comprehensions): 리스트, 딕셔너리, 세트를 만드는 간결한 방법

컴프리헨션(comprehensions)은 Python의 가장 우아한 기능 중 하나로, 한 줄의 읽기 쉬운 코드로 컬렉션(collection)을 생성하고 변환할 수 있게 해줍니다. 반복문(loop)과 append 연산을 여러 줄로 작성하는 대신, 컴프리헨션을 사용하면 같은 로직을 더 간결하게, 그리고 종종 더 명확하게 표현할 수 있습니다.

이 장에서는 리스트 컴프리헨션(list comprehensions), 딕셔너리 컴프리헨션(dictionary comprehensions), 세트 컴프리헨션(set comprehensions)을 사용해 더 Pythonic한 코드를 작성하는 방법을 살펴봅니다. 조건 로직을 어떻게 포함하는지, 전통적인 반복문 대신 컴프리헨션을 언제 선택해야 하는지, 중첩 반복을 사용한 더 복잡한 시나리오를 어떻게 처리하는지도 보겠습니다.

34.1) 리스트를 생성하고 변환하는 리스트 컴프리헨션(List Comprehensions)

34.1.1) 기본 리스트 컴프리헨션 문법

리스트 컴프리헨션(list comprehension)은 기존 시퀀스(sequence)의 각 항목에 표현식(expression)을 적용해 새로운 리스트(list)를 만드는 간결한 방법을 제공합니다. 기본 문법은 다음과 같습니다:

python
[expression for item in iterable]

이것은 iterable(리스트, range, 문자열 등 루프를 돌릴 수 있는 모든 시퀀스)의 각 item에 대해 expression을 평가한 결과로 새로운 리스트를 생성합니다.

간단한 예제로 시작해 보겠습니다. 0부터 4까지의 숫자에 대한 제곱 리스트를 만들고 싶다고 가정해 봅시다:

python
# 반복문을 사용하는 전통적인 방식
squares = []
for number in range(5):
    squares.append(number ** 2)
print(squares)  # Output: [0, 1, 4, 9, 16]

리스트 컴프리헨션을 사용하면 더 간결하게 표현할 수 있습니다:

python
# 리스트 컴프리헨션 사용
squares = [number ** 2 for number in range(5)]
print(squares)  # Output: [0, 1, 4, 9, 16]

두 방식 모두 같은 결과를 만들지만, 컴프리헨션이 더 콤팩트하며, 문법에 익숙해지면 읽기도 더 쉽습니다. 컴프리헨션은 제곱된 값들의 리스트를 만들고 있다는 점을 명확하게 보여줍니다.

34.1.2) 기존 데이터 변환하기

리스트 컴프리헨션은 데이터를 한 형태에서 다른 형태로 변환하는 데 뛰어납니다. 실용적인 예제를 몇 가지 살펴보겠습니다.

섭씨를 화씨로 변환하기:

python
# 섭씨 온도 데이터
celsius_temps = [0, 10, 20, 30, 40]
 
# 공식 F = C * 9/5 + 32 를 사용해 화씨로 변환
fahrenheit_temps = [temp * 9/5 + 32 for temp in celsius_temps]
print(fahrenheit_temps)  # Output: [32.0, 50.0, 68.0, 86.0, 104.0]

문자열을 대문자로 변환하기:

python
# 대소문자가 섞인 제품 코드
product_codes = ["abc123", "def456", "ghi789"]
 
# 대문자로 표준화
uppercase_codes = [code.upper() for code in product_codes]
print(uppercase_codes)  # Output: ['ABC123', 'DEF456', 'GHI789']

34.1.3) range 객체로부터 리스트 만들기

리스트 컴프리헨션은 12장에서 배운 range()와 자연스럽게 함께 사용됩니다. 이는 특정 패턴을 가진 시퀀스를 생성할 때 유용합니다:

python
# 0부터 10까지 짝수 생성
evens = [n * 2 for n in range(6)]  # n은 0부터 5까지이므로, n*2는 0, 2, 4, 6, 8, 10
print(evens)  # Output: [0, 2, 4, 6, 8, 10]
 
# 5의 배수 생성
multiples_of_five = [n * 5 for n in range(1, 6)]
print(multiples_of_five)  # Output: [5, 10, 15, 20, 25]

34.1.4) 컴프리헨션 vs append로 리스트 만들기

리스트 컴프리헨션은 한 번의 연산으로 전체 리스트를 생성하는 반면, 전통적인 반복문 방식은 리스트를 점진적으로 만들어 나간다는 점을 이해하는 것이 중요합니다. 둘 다 같은 결과를 만들지만, 일반적으로 컴프리헨션이 새 리스트를 만드는 데 더 빠르고 더 Pythonic한 방식으로 여겨집니다.

다음은 나란히 비교한 예시입니다:

python
# 전통적인 반복문 방식
result = []
for i in range(5):
    result.append(i * 3)
print(result)  # Output: [0, 3, 6, 9, 12]
 
# 리스트 컴프리헨션 방식
result = [i * 3 for i in range(5)]
print(result)  # Output: [0, 3, 6, 9, 12]

두 방식 모두 유효하지만, 컴프리헨션이 더 간결하고 의도를 명확하게 표현합니다: “각 값이 i * 3인 값들의 리스트를 만든다.”

34.2) 리스트 컴프리헨션 안의 조건 로직(Conditional Logic)

34.2.1) if 조건으로 필터링하기

리스트 컴프리헨션의 가장 강력한 기능 중 하나는 조건에 따라 항목을 필터링할 수 있다는 점입니다. 컴프리헨션 끝에 if 절을 추가해 특정 기준을 만족하는 항목만 포함할 수 있습니다:

python
[expression for item in iterable if condition]

if 절은 필터 역할을 합니다: Python은 각 항목에 대해 조건을 평가하고, 조건이 True인 항목만 결과 리스트에 포함됩니다. 조건을 만족하지 못하는 항목은 완전히 건너뜁니다.

간단한 예제로 확인해 보겠습니다:

python
# 0부터 9까지에서 짝수만 가져오기
numbers = range(10)
evens = [n for n in numbers if n % 2 == 0]
print(evens)  # Output: [0, 2, 4, 6, 8]

여기서 n % 2 == 0은 숫자가 짝수인지 확인합니다. 이 테스트를 통과한 숫자만 새 리스트에 포함됩니다.

학생 점수 필터링:

python
# 학생 시험 점수
scores = [45, 78, 92, 65, 88, 55, 73, 95]
 
# 합격 점수(>= 70)만 가져오기
passing_scores = [score for score in scores if score >= 70]
print(passing_scores)  # Output: [78, 92, 88, 73, 95]

34.2.2) 필터링된 항목 변환하기

필터링된 항목에 표현식을 적용하여 필터링과 변환을 결합할 수 있습니다:

python
# 학생 점수
scores = [45, 78, 92, 65, 88, 55, 73, 95]
 
# 합격 점수를 가져와서 0-10 범위로 조정
scaled_passing = [score / 10 for score in scores if score >= 70]
print(scaled_passing)  # Output: [7.8, 9.2, 8.8, 7.3, 9.5]
# 먼저 필터링(>= 70만 유지), 그 다음 변환(10으로 나눔)

문자열 변환과 필터링:

python
# 다양한 품질의 제품 이름
products = ["apple", "BANANA", "cherry", "DATE", "elderberry"]
 
# 이름이 5자보다 긴 제품의 대문자 버전 가져오기
long_products_upper = [product.upper() for product in products if len(product) > 5]
print(long_products_upper)  # Output: ['BANANA', 'CHERRY', 'ELDERBERRY']

34.2.3) Comprehension에서 조건 표현식(if-else) 사용하기

때로는 항목을 필터링하는 대신, 조건에 따라 다르게 변환하고 싶을 때가 있습니다. 이를 위해 comprehension의 표현식 부분에 조건 표현식(Chapter 10에서 배움)을 사용합니다:

python
[expression_if_true if condition else expression_if_false for item in iterable]

이것은 필터링과 다릅니다. 여기서는 모든 항목이 결과에 포함됩니다—if-else는 각 항목에 적용할 표현식을 결정합니다. 조건 표현식(Chapter 10에서 배움)은 for 절 앞의 표현식 부분에 나타납니다.

문법 차이를 주목하세요:

  • 필터링: [expr for item in seq if condition] - 끝에 if, else 없음
  • 조건 표현식: [expr_if if cond else expr_else for item in seq] - 표현식에 if-else, for
python
# 숫자를 짝수 또는 홀수로 분류
numbers = range(6)
classifications = ["even" if n % 2 == 0 else "odd" for n in numbers]
print(classifications)  # Output: ['even', 'odd', 'even', 'odd', 'even', 'odd']

조건에 따라 다른 변환 적용:

python
# 학생 점수
scores = [45, 78, 92, 65, 88, 55, 73, 95]
 
# 불합격 점수에 보너스 점수 추가, 합격 점수는 그대로 유지
adjusted_scores = [score + 10 if score < 70 else score for score in scores]
print(adjusted_scores)  # Output: [55, 78, 92, 75, 88, 65, 73, 95]

두 예제 모두에서 주목할 점:

  • 원본 리스트의 모든 항목이 결과에 나타남
  • if-else가 각 항목이 어떤 값이 될지 결정
  • 필터링되어 제외되는 항목 없음

34.2.4) 차이점 이해하기: 필터링 vs 조건 표현식

이 두 패턴의 차이를 이해하는 것이 중요합니다:

필터링 (끝에 if) - 일부 항목 제외:

python
# 양수만 포함
numbers = [-2, 5, -1, 8, 0, 3]
positives = [n for n in numbers if n > 0]
print(positives)  # Output: [5, 8, 3]
print(len(positives))  # Output: 3 (3개 항목만)
# 처리 과정: 조건 확인 → True면 포함 → False면 건너뜀

조건 표현식 (표현식에 if-else) - 모든 항목 포함되지만 다르게 변환:

python
# 음수를 0으로 변환, 양수는 유지
numbers = [-2, 5, -1, 8, 0, 3]
non_negatives = [n if n > 0 else 0 for n in numbers]
print(non_negatives)  # Output: [0, 5, 0, 8, 0, 3]
print(len(non_negatives))  # Output: 6 (6개 항목 모두)
# 처리 과정: 조건 확인 → True면 첫 표현식 → False면 두 번째 표현식 → 결과 항상 포함

끝에
else 없음

표현식에
else와 함께

조건이 있는
List Comprehension

if는
어디에?

필터링

조건 표현식

[x for x in items if condition]

[x if condition else y for x in items]

일부 항목 제외
결과가 더 짧을 수 있음

모든 항목 포함
원본과 같은 길이

34.3) 딕셔너리 컴프리헨션(Dictionary Comprehensions)

34.3.1) 기본 딕셔너리 컴프리헨션 문법

리스트 컴프리헨션이 리스트를 만드는 것처럼, 딕셔너리 컴프리헨션(dictionary comprehension)은 딕셔너리(dictionary)를 만듭니다. 문법은 비슷하지만 키(key)와 값(value)을 모두 지정합니다:

python
{key_expression: value_expression for item in iterable}

이는 iterable로부터 생성된 각 키-값 쌍으로 새로운 딕셔너리를 만듭니다.

숫자를 그 제곱에 매핑하는 딕셔너리를 만드는 간단한 예제로 시작해 보겠습니다:

python
# 숫자와 그 제곱으로 이루어진 딕셔너리 만들기
squares_dict = {n: n ** 2 for n in range(5)}
print(squares_dict)  # Output: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

두 리스트로부터 딕셔너리 만들기:

python
# 학생 이름과 점수
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
 
# 이름을 점수에 매핑하는 딕셔너리 만들기
student_scores = {names[i]: scores[i] for i in range(len(names))}
print(student_scores)  # Output: {'Alice': 85, 'Bob': 92, 'Charlie': 78}

두 시퀀스를 결합하는 더 우아한 방법은 37장에서 배울 zip()을 사용하는 것입니다. 지금은 인덱스 기반 접근 방식도 잘 동작합니다.

34.3.2) 기존 딕셔너리 변환하기

Dictionary comprehension은 기존 딕셔너리를 변환하는 데 탁월합니다. 키, 값 또는 둘 다를 수정할 수 있습니다.

Comprehension에서 딕셔너리를 순회할 때, 키와 값 모두에 접근하려면 .items()를 사용합니다. .items() 메서드는 for 절에서 unpacking할 수 있는 키-값 쌍을 반환합니다:

python
# 달러 단위의 원래 가격
prices = {"apple": 1.50, "banana": 0.75, "cherry": 2.00}
 
# 센트로 변환(100을 곱함)
prices_in_cents = {fruit: price * 100 for fruit, price in prices.items()}
print(prices_in_cents)  # Output: {'apple': 150.0, 'banana': 75.0, 'cherry': 200.0}

키 변환하기:

python
# 소문자 제품 코드
codes = {"abc": 100, "def": 200, "ghi": 300}
 
# 키를 대문자로 변환
uppercase_codes = {code.upper(): quantity for code, quantity in codes.items()}
print(uppercase_codes)  # Output: {'ABC': 100, 'DEF': 200, 'GHI': 300}

키와 값을 모두 변환하기:

python
# 학생 이름과 점수
scores = {"alice": 85, "bob": 92, "charlie": 78}
 
# 이름을 대문자로 시작하게 바꾸고 점수를 0-10 범위로 스케일링
formatted_scores = {name.capitalize(): score / 10 for name, score in scores.items()}
print(formatted_scores)  # Output: {'Alice': 8.5, 'Bob': 9.2, 'Charlie': 7.8}

34.3.3) 딕셔너리 항목 필터링하기

리스트 컴프리헨션처럼, 딕셔너리 컴프리헨션도 조건을 포함해 항목을 필터링할 수 있습니다:

python
# 학생 점수
scores = {"Alice": 85, "Bob": 65, "Charlie": 92, "David": 55, "Eve": 78}
 
# 합격 점수(>= 70)만 가져오기
passing_scores = {name: score for name, score in scores.items() if score >= 70}
print(passing_scores)  # Output: {'Alice': 85, 'Charlie': 92, 'Eve': 78}

키의 특성으로 필터링하기:

python
# 제품 재고
inventory = {"apple": 50, "banana": 30, "apricot": 20, "cherry": 40}
 
# 'a'로 시작하는 제품만 가져오기
a_products = {product: quantity for product, quantity in inventory.items() 
              if product.startswith('a')}
print(a_products)  # Output: {'apple': 50, 'apricot': 20}

34.3.4) 시퀀스에서 딕셔너리 만들기

딕셔너리 컴프리헨션은 시퀀스로부터 조회용 딕셔너리를 만드는 데 유용합니다:

python
# 단어 리스트
words = ["python", "java", "ruby", "javascript"]
 
# 각 단어를 그 길이에 매핑하는 딕셔너리 만들기
word_lengths = {word: len(word) for word in words}
print(word_lengths)  # Output: {'python': 6, 'java': 4, 'ruby': 4, 'javascript': 10}

34.3.5) 딕셔너리 컴프리헨션에서 조건 표현식 사용하기

조건에 따라 값을 다르게 계산하기 위해 조건 표현식을 사용할 수 있습니다:

python
# 학생 점수
scores = {"Alice": 85, "Bob": 65, "Charlie": 92, "David": 55}
 
# "Pass" 또는 "Fail" 상태 추가
scores_with_status = {name: "Pass" if score >= 70 else "Fail" 
                      for name, score in scores.items()}
print(scores_with_status)  # Output: {'Alice': 'Pass', 'Bob': 'Fail', 'Charlie': 'Pass', 'David': 'Fail'}

서로 다른 변환 적용하기:

python
# 제품 가격
prices = {"apple": 1.50, "banana": 0.75, "cherry": 2.50}
 
# 비싼 항목(> $2.00)에 할인 적용
discounted_prices = {product: price * 0.9 if price > 2.00 else price 
                     for product, price in prices.items()}
print(discounted_prices)  # Output: {'apple': 1.5, 'banana': 0.75, 'cherry': 2.25}

34.4) 세트 컴프리헨션(Set Comprehensions)

34.4.1) 기본 세트 컴프리헨션 문법

세트 컴프리헨션(set comprehension)은 리스트 컴프리헨션과 유사한 문법으로 세트(set)를 만들되, 중괄호를 사용합니다:

python
{expression for item in iterable}

결과는 집합이므로 중복 값이 자동으로 제거되고 순서가 보장되지 않습니다.

python
# 제곱 세트 만들기
squares_set = {n ** 2 for n in range(6)}
print(squares_set)  # Output: {0, 1, 4, 9, 16, 25}

리스트 컴프리헨션과의 핵심 차이는 세트가 자동으로 중복을 제거한다는 점입니다:

python
# 리스트 컴프리헨션 - 중복 유지
numbers = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
squared_list = [n ** 2 for n in numbers]
print(squared_list)  # Output: [1, 4, 4, 9, 9, 9, 16, 16, 16, 16]
 
# 세트 컴프리헨션 - 중복 제거
squared_set = {n ** 2 for n in numbers}
print(squared_set)  # Output: {16, 1, 4, 9} (순서는 다를 수 있음)

집합 출력 순서는 여기에 표시된 것과 다를 수 있습니다. 집합은 순서가 없는 컬렉션이므로 Python은 요소를 임의의 순서로 표시할 수 있습니다.

34.4.2) 고유 값 추출하기

세트 컴프리헨션은 컬렉션에서 고유한 값만 추출해야 할 때 완벽합니다:

python
# 학생 응답(중복 포함)
responses = ["yes", "no", "yes", "maybe", "no", "yes", "maybe"]
 
# 고유한 응답 가져오기
unique_responses = {response for response in responses}
print(unique_responses)  # Output: {'maybe', 'yes', 'no'}

문자열에서 고유한 문자 추출하기:

python
# 반복 문자가 있는 텍스트
text = "mississippi"
 
# 고유한 문자 가져오기
unique_chars = {char for char in text}
print(unique_chars)  # Output: {'m', 'i', 's', 'p'}

34.4.3) 세트 컴프리헨션으로 변환 및 필터링하기

다른 컴프리헨션과 마찬가지로, 세트 컴프리헨션도 변환과 조건을 포함할 수 있습니다:

python
# 학생 이름
names = ["Alice", "bob", "CHARLIE", "david", "EVE"]
 
# 첫 글자를 대문자로 만든 고유 값 가져오기
first_letters = {name[0].upper() for name in names}
print(first_letters)  # Output: {'A', 'B', 'C', 'D', 'E'}

조건으로 필터링하기:

python
# 중복이 있는 숫자
numbers = [1, -2, 3, -4, 5, -2, 3, 6, -4]
 
# 고유한 양수만 가져오기
positive_numbers = {n for n in numbers if n > 0}
print(positive_numbers)  # Output: {1, 3, 5, 6}

34.4.4) 세트 컴프리헨션이 가장 유용할 때

세트 컴프리헨션은 특히 다음과 같은 경우에 가치가 큽니다:

  1. 고유한 값이 필요할 때: 중복이 자동으로 제거됩니다
  2. 순서가 중요하지 않을 때: 세트는 순서가 없으므로, 시퀀스가 중요하지 않을 때 사용합니다
  3. 세트 연산을 수행할 때: 결과를 합집합(union), 교집합(intersection) 등과 함께 사용할 수 있습니다(17장에서 배운 내용입니다)
python
# 두 과목에 등록한 학생들
course_a = ["Alice", "Bob", "Charlie", "David"]
course_b = ["Charlie", "David", "Eve", "Frank"]
 
# 세트 컴프리헨션으로 두 과목 전체의 고유 학생 가져오기
all_students = {student for course in [course_a, course_b] for student in course}
print(all_students)  # Output: {'Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'}

34.5) 컴프리헨션 vs 반복문 선택하기

34.5.1) 컴프리헨션이 더 나은 경우

컴프리헨션은 기존 컬렉션을 변환하거나 필터링해 새 컬렉션을 만들 때 일반적으로 선호됩니다. 더 간결하고, 종종 더 읽기 쉬우며, 보통 동일한 반복문보다 빠릅니다.

컴프리헨션이 뛰어난 경우:

  1. 기존 컬렉션으로부터 새 컬렉션을 만들 때:
python
# 컴프리헨션을 잘 사용한 예
prices = [10.99, 25.50, 8.75, 15.00]
discounted = [price * 0.9 for price in prices]
  1. 변환이 단순할 때:
python
# 명확하고 간결함
names = ["alice", "bob", "charlie"]
uppercase_names = [name.upper() for name in names]
  1. 단순 조건으로 필터링할 때:
python
# 이해하기 쉬움
scores = [85, 92, 78, 65, 88, 55, 73, 95]
passing = [score for score in scores if score >= 70]

34.5.2) 전통적인 반복문이 더 나은 경우

하지만 전통적인 반복문이 더 적절하고 읽기 쉬운 상황도 있습니다:

다음과 같은 경우에는 반복문을 사용하세요:

  1. 로직이 복잡하거나 여러 단계가 필요한 경우:
python
# 컴프리헨션으로는 너무 복잡함
results = []
for score in scores:
    if score >= 90:
        grade = "A"
    elif score >= 80:
        grade = "B"
    elif score >= 70:
        grade = "C"
    else:
        grade = "F"
    results.append({"score": score, "grade": grade})

이것을 컴프리헨션으로 쓸 수도 있지만, 읽기가 훨씬 어려워질 것입니다.

  1. 컬렉션을 만드는 것 이상의 작업을 수행해야 하는 경우:
python
# I/O나 부수 효과(side effects)를 수행할 때는 반복문이 더 명확함
for filename in files:
    with open(filename) as f:
        content = f.read()
        print(f"Processing {filename}")
        # ... more processing
  1. 기존 컬렉션을 제자리(in place)에서 수정해야 하는 경우:
python
# 리스트를 제자리에서 수정 - 컴프리헨션은 사용할 수 없음
numbers = [1, 2, 3, 4, 5]
for i in range(len(numbers)):
    numbers[i] *= 2
print(numbers)  # Output: [2, 4, 6, 8, 10]
  1. 복잡한 로직과 함께 break 또는 continue를 사용해야 하는 경우:
python
# 추가 처리가 있는 첫 번째 발생 찾기
found = None
for item in items:
    if item.startswith("target"):
        found = item
        print(f"Found: {found}")
        break

34.5.3) 가독성 고려 사항

가장 중요한 요소는 가독성입니다. 컴프리헨션이 너무 길거나 복잡해지면 전통적인 반복문으로 나누세요:

python
# 읽기 어려움 - 한 줄에 너무 많은 일이 벌어짐
result = [item.upper().strip() for item in items if len(item) > 5 and item.startswith('a')]
 
# 더 나음 - 로직이 복잡할 때는 반복문 사용
result = []
for item in items:
    if len(item) > 5 and item.startswith('a'):
        cleaned = item.strip().upper()
        result.append(cleaned)

경험칙(rule of thumb): 컴프리헨션이 한 줄(또는 명확한 포맷으로 최대 두 줄)에 깔끔하게 들어가지 않는다면, 반복문을 사용하는 것을 고려하세요.

34.5.4) 성능 고려 사항

컴프리헨션은 인터프리터 수준에서 최적화되기 때문에 일반적으로 동일한 반복문보다 빠릅니다. 하지만 이 성능 차이는 보통 작은~중간 크기의 컬렉션에서는 무시할 수 있을 정도입니다.

python
# 둘 다 같은 결과를 만듦
# 컴프리헨션이 약간 더 빠름
squares_comp = [n ** 2 for n in range(1000)]
 
# 반복문은 약간 더 느리지만 더 유연함
squares_loop = []
for n in range(1000):
    squares_loop.append(n ** 2)

실무적으로는 성능보다는 가독성을 기준으로 선택하세요. 프로파일링(profiling)을 통해 특정 연산이 병목(bottleneck)임이 확인될 때만 속도 최적화를 하세요.

34.5.5) 접근 방식 결합하기

때로는 두 접근 방식을 결합하는 것이 최선의 해결책입니다:

python
# 간단한 변환에는 컴프리헨션 사용
student_data = [
    {"name": "Alice", "score": 85},
    {"name": "Bob", "score": 92},
    {"name": "Charlie", "score": 78}
]
 
# 컴프리헨션으로 점수 추출
scores = [student["score"] for student in student_data]
 
# 복잡한 처리는 반복문 사용
for student in student_data:
    score = student["score"]
    if score >= 90:
        print(f"{student['name']}: Excellent!")
    elif score >= 80:
        print(f"{student['name']}: Good job!")
    else:
        print(f"{student['name']}: Keep working!")

Yes

No

Yes

No

Yes

No

Yes

No

컬렉션을 만들어야 하나?

단순 변환
또는 필터링인가?

컴프리헨션 사용

복잡한 로직 또는
여러 단계인가?

반복문 사용

부수 효과
또는 I/O가 필요한가?

1~2줄에 들어가고
읽기 쉬운가?

34.6) 중첩 반복문과 여러 for 절

34.6.1) 여러 for 절 이해하기

컴프리헨션에는 여러 개의 for 절이 포함될 수 있으며, 이는 중첩 반복문(nested loops)과 동일합니다. 문법은 다음과 같습니다:

python
[expression for item1 in iterable1 for item2 in iterable2]

이는 다음과 동일합니다:

python
result = []
for item1 in iterable1:
    for item2 in iterable2:
        result.append(expression)

핵심은 for 절이 왼쪽에서 오른쪽으로 읽힌다는 점이며, 이는 중첩 반복문이 위에서 아래로 작성되는 것과 같습니다.

두 리스트의 모든 조합을 만드는 간단한 예제로 시작해 보겠습니다:

python
# 값 두 리스트
colors = ["red", "blue"]
sizes = ["S", "M", "L"]
 
# 모든 조합 만들기
combinations = [(color, size) for color in colors for size in sizes]
print(combinations)
# Output: [('red', 'S'), ('red', 'M'), ('red', 'L'), ('blue', 'S'), ('blue', 'M'), ('blue', 'L')]

이는 색상과 사이즈의 가능한 모든 페어링을 만듭니다.

34.6.2) 좌표 쌍 만들기

흔한 사용 사례는 좌표 쌍을 생성하는 것입니다:

python
# 3x3 좌표 그리드 만들기
coordinates = [(x, y) for x in range(3) for y in range(3)]
print(coordinates)
# Output: [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]

구구단(곱셈 표) 만들기:

python
# 곱셈 쌍 생성
products = [(x, y, x * y) for x in range(1, 4) for y in range(1, 4)]
for x, y, product in products:
    print(f"{x} × {y} = {product}")
# Output:
# 1 × 1 = 1
# 1 × 2 = 2
# 1 × 3 = 3
# 2 × 1 = 2
# 2 × 2 = 4
# 2 × 3 = 6
# 3 × 1 = 3
# 3 × 2 = 6
# 3 × 3 = 9

34.6.3) 중첩 리스트 펼치기(Flattening)

여러 for 절은 중첩 구조를 펼치는 데 유용합니다:

python
# 숫자의 중첩 리스트
nested_numbers = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
 
# 하나의 리스트로 펼치기
flat = [num for sublist in nested_numbers for num in sublist]
print(flat)  # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]

이는 다음과 동일합니다:

python
flat = []
for sublist in nested_numbers:
    for num in sublist:
        flat.append(num)

단어 리스트를 문자로 펼치기:

python
# 단어 리스트
words = ["cat", "dog", "bird"]
 
# 모든 단어의 모든 문자 가져오기
all_chars = [char for word in words for char in word]
print(all_chars)  # Output: ['c', 'a', 't', 'd', 'o', 'g', 'b', 'i', 'r', 'd']

34.6.4) 중첩 컴프리헨션에 조건 추가하기

조건을 추가해 결과를 필터링할 수 있습니다:

python
# 합이 짝수인 쌍 만들기
pairs = [(x, y) for x in range(5) for y in range(5) if (x + y) % 2 == 0]
print(pairs)
# Output: [(0, 0), (0, 2), (0, 4), (1, 1), (1, 3), (2, 0), (2, 2), (2, 4), (3, 1), (3, 3), (4, 0), (4, 2), (4, 4)]

리스트 간 공통 원소 찾기:

python
# 숫자 두 리스트
list1 = [1, 2, 3, 4, 5]
list2 = [4, 5, 6, 7, 8]
 
# 값이 같은(공통 원소인) 쌍 찾기
common = [x for x in list1 for y in list2 if x == y]
print(common)  # Output: [4, 5]

참고: 공통 원소를 찾는 경우, 세트 교집합을 사용하는 것이 더 효율적입니다: set(list1) & set(list2)(17장에서 배웠습니다).

34.6.5) 중첩 딕셔너리 컴프리헨션

딕셔너리 컴프리헨션에서도 여러 for 절을 사용할 수 있습니다:

python
# 좌표 합의 딕셔너리 만들기
coord_sums = {(x, y): x + y for x in range(3) for y in range(3)}
print(coord_sums)
# Output: {(0, 0): 0, (0, 1): 1, (0, 2): 2, (1, 0): 1, (1, 1): 2, (1, 2): 3, (2, 0): 2, (2, 1): 3, (2, 2): 4}

34.6.6) 중첩 컴프리헨션을 피해야 할 때

중첩 컴프리헨션은 강력하지만, 금방 읽기 어려워질 수 있습니다. 다음 가이드라인을 고려하세요:

허용 가능 - 비교적 단순함:

python
# 2단계 중첩, 단순한 표현식
matrix = [[i * j for j in range(3)] for i in range(3)]
print(matrix)  # Output: [[0, 0, 0], [0, 1, 2], [0, 2, 4]]

복잡해짐 - 반복문 고려:

python
# 3단계 중첩 - 읽기 어려움
result = [[[i + j + k for k in range(2)] for j in range(2)] for i in range(2)]
# Better as nested loops for clarity

경험칙: for 절이 두 개를 초과하거나, 표현식이 복잡하다면 전통적인 중첩 반복문을 대신 사용하세요:

python
# 명시적인 반복문이 더 명확함
result = []
for i in range(2):
    middle = []
    for j in range(2):
        inner = []
        for k in range(2):
            inner.append(i + j + k)
        middle.append(inner)
    result.append(middle)

여러 for 절이 있는 컴프리헨션은 강력한 도구이지만, 기억하세요: 간결함보다 명확성이 더 중요합니다. 중첩 컴프리헨션이 이해하기 어려워진다면, 명시적인 반복문을 사용하는 것이 더 좋습니다.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai