람다와 컴프리헨션
이 세 가지 기능은 공통점이 있습니다. 평소라면 여러 줄이 필요한 아이디어를 읽기 쉬운 단일 표현식으로 표현할 수 있게 해줍니다. 잘 사용하면 코드가 더 짧고 명확해집니다. 잘못 사용하면 읽을 수 없게 됩니다. 이 장에서는 각각을 언제 사용하고 언제 멈춰야 하는지 다룹니다.
람다 함수
람다는 이름이 없는, 표현식 하나로 된 함수입니다. lambda 키워드로 만듭니다. 진짜 유용한 점은 이름 있는 함수를 먼저 정의할 필요 없이, 필요한 곳에 인라인으로 작성할 수 있다는 것입니다. 그래서 sorted()와 함께 쓰면 유용합니다.
double = lambda x: x * 2
double(5) # 10다음과 동일합니다:
def double(x):
return x * 2대부분의 경우 def를 사용하세요. 람다의 진짜 장점 하나는 이름 없이 필요한 곳에 인라인으로 작성할 수 있다는 점입니다. 이것이 sorted(), map(), filter()와 함께 사용할 때 유용한 이유입니다:
players = [("민준", 87), ("서연", 74), ("지호", 92)]
sorted(players, key=lambda p: p[1]) # 점수 오름차순 정렬
sorted(players, key=lambda p: p[1], reverse=True) # 점수 내림차순 정렬람다가 없다면 key= 인수만을 위해 이름 있는 함수를 정의해야 합니다. 람다는 의도를 지역적이고 가시적으로 유지합니다.
람다는 여러 인수를 받을 수 있습니다:
add = lambda a, b: a + b
add(3, 4) # 7람다를 사용할 때: 한 곳에서만 쓰이는 단순한 표현식일 때만 사용하세요. 복잡해지거나 재사용이 필요하다면 제대로 된 def를 작성하세요. 여러 연산자에 걸치거나 조건문이 필요한 람다는 보통 def로 전환하라는 신호입니다.
리스트 컴프리헨션
Python에서 가장 일반적인 변환입니다. 시퀀스를 받아서 각 항목에 무언가를 하고 새 리스트를 얻습니다. 리스트 컴프리헨션은 이를 읽기 쉬운 한 줄로 처리합니다: [expression for item in iterable]. if로 필터도 추가할 수 있습니다.
긴 방법:
numbers = [1, 2, 3, 4, 5]
squares = []
for n in numbers:
squares.append(n ** 2)리스트 컴프리헨션:
squares = [n ** 2 for n in numbers]구조는 언제나 같습니다: [expression for item in iterable].
scores = [87, 42, 96, 55, 71]
scaled = [s * 1.1 for s in scores] # 10% 보너스 적용
as_grades = [f"{s}/100" for s in scores] # 각 항목 포맷팅조건으로 필터링
테스트를 통과한 항목만 포함하려면 if 절을 추가하세요. 결과는 조건이 True인 항목만 담긴 새 리스트입니다.
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
evens = [n for n in numbers if n % 2 == 0] # [2, 4, 6, 8]
odds = [n for n in numbers if n % 2 != 0] # [1, 3, 5, 7]scores = [87, 42, 96, 55, 71, 38]
passing = [s for s in scores if s >= 60] # [87, 96, 71]
failing = [s for s in scores if s < 60] # [42, 55, 38]중첩 컴프리헨션
컴프리헨션을 중첩하여 리스트의 리스트를 하나의 리스트로 평탄화할 수 있습니다. 왼쪽에서 오른쪽으로 읽으세요. 각 행에 대해, 그 행의 각 항목에 대해, 그 항목을 포함합니다.
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [item for row in matrix for item in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]왼쪽에서 오른쪽으로 읽으세요: matrix의 각 row에 대해, row의 각 item에 대해, item을 포함합니다.
중첩 컴프리헨션은 금세 헷갈릴 수 있습니다. 파싱하는 데 잠깐 이상 걸린다면 루프를 명시적으로 작성하세요.
딕셔너리 컴프리헨션
딕셔너리 컴프리헨션은 리스트 컴프리헨션과 같은 아이디어로 한 표현식에서 딕셔너리를 만듭니다: {key: value for item in iterable}. 리스트 컴프리헨션과 마찬가지로 if로 필터를 추가할 수 있습니다.
names = ["민준", "서연", "지호"]
scores = [87, 74, 92]
score_map = {name: score for name, score in zip(names, scores)}
# {"민준": 87, "서연": 74, "지호": 92}필터와 함께:
passing = {name: score for name, score in score_map.items() if score >= 80}
# {"민준": 87, "지호": 92}words = ["apple", "banana", "cherry"]
word_lens = {word: len(word) for word in words}
# {"apple": 5, "banana": 6, "cherry": 6}셋 컴프리헨션
셋 컴프리헨션은 콜론 없이 중괄호를 사용하여 한 표현식에서 집합을 만듭니다. 결과가 집합이므로 중복은 자동으로 제거됩니다.
words = ["apple", "banana", "cherry", "apple"]
unique = {w.lower() for w in words} # {"apple", "banana", "cherry"}유일한 값이 필요하고 순서는 상관없을 때 셋 컴프리헨션을 사용하세요.
제너레이터 표현식
제너레이터는 대괄호 대신 괄호를 사용하는 리스트 컴프리헨션처럼 보입니다. 핵심 차이는 다음과 같습니다. 리스트 컴프리헨션은 전체 리스트를 한 번에 메모리에 만듭니다. 제너레이터는 필요할 때만 한 번에 하나씩 값을 생성합니다. 큰 시퀀스의 경우 메모리를 훨씬 적게 사용합니다.
squares_gen = (n ** 2 for n in range(1000000))total = sum(n ** 2 for n in range(1000000)) # sum()이 제너레이터를 소비함sum(), max(), min(), any() 같은 함수에 제너레이터를 직접 전달할 때는 추가 괄호를 생략할 수 있습니다:
total = sum(n ** 2 for n in range(1000)) # 괄호 두 쌍이 아니라 한 쌍대부분의 일상적인 코드에서는 리스트 컴프리헨션으로 충분합니다. 모든 것을 메모리에 두면 낭비가 될 큰 데이터셋이나 스트리밍 데이터를 처리할 때 제너레이터를 사용하세요.
zip()
zip()은 두 개 이상의 시퀀스의 항목을 짝지어 병렬로 순회할 수 있게 해줍니다. 가장 짧은 시퀀스에서 멈춥니다. 두 리스트가 서로 대응할 때 인덱스를 관리하지 않는 깔끔한 방법입니다.
names = ["민준", "서연", "지호"]
scores = [87, 74, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# 민준: 87
# 서연: 74
# 지호: 92zip()은 가장 짧은 시퀀스에서 멈춥니다. 시퀀스 길이가 다를 수 있다면 채울 값과 함께 itertools.zip_longest()를 사용하세요.
짝지어진 페어 리스트를 다시 두 개의 별도 리스트로 변환하려면 zip(*pairs)를 사용하세요:
pairs = [("민준", 87), ("서연", 74), ("지호", 92)]
names, scores = zip(*pairs)
# names = ("민준", "서연", "지호")
# scores = (87, 74, 92)여기서 *는 무엇을 하나요?
*pairs는 리스트를 별도의 인수로 언패킹합니다: zip(*pairs)는 zip(("민준", 87), ("서연", 74), ("지호", 92))가 됩니다. * 연산자는 함수 장에서 다룹니다.
zip()은 또한 인덱스를 수동으로 관리하지 않고 여러 시퀀스를 병렬로 순회하는 깔끔한 방법입니다:
before = [10, 20, 30]
after = [15, 18, 35]
for b, a in zip(before, after):
change = a - b
print(f"{b} -> {a} ({'+' if change >= 0 else ''}{change})")map()과 filter()
map()과 filter()는 컴프리헨션이 하는 일을 수행하는 오래된 함수형 스타일 도구입니다. 오래된 코드에서 볼 수 있으므로 의미를 알아두는 것이 좋습니다. 새 코드에서는 컴프리헨션을 선호하세요. 대부분의 Python 개발자에게 더 읽기 쉽습니다.
numbers = [1, 2, 3, 4, 5]
list(map(lambda x: x ** 2, numbers)) # [1, 4, 9, 16, 25]
list(filter(lambda x: x % 2 == 0, numbers)) # [2, 4]컴프리헨션을 선호하세요. 대부분의 Python 개발자에게 더 읽기 쉽습니다. 이미 존재하는 이름 있는 함수가 있을 때 map()을 사용하세요:
strings = ["1", "2", "3"]
numbers = list(map(int, strings)) # [1, 2, 3] (여기서는 컴프리헨션보다 깔끔함)실전에서
플레이어 목록을 합격 점수로 필터링하고, sorted와 람다로 점수 순으로 정렬한 다음, 순위를 매겨 출력합니다:
players = [
{"name": "민준", "score": 87},
{"name": "서연", "score": 42},
{"name": "지호", "score": 96},
{"name": "수아", "score": 55},
]
passing = [p for p in players if p["score"] >= 60]
ranked = sorted(passing, key=lambda p: p["score"], reverse=True)
score_map = {p["name"]: p["score"] for p in ranked}
for i, (name, score) in enumerate(score_map.items(), start=1):
print(f"{i}. {name}: {score}")
