람다 함수와 컴프리헨션

이 세 기능은 공통점이 있습니다. 여러 줄이 필요한 생각을 하나의 읽기 좋은 식으로 표현할 수 있게 해줍니다. 잘 사용하면 코드가 짧고 명확해집니다. 잘못 사용하면 읽기 어려워집니다. 이 장에서는 각 기능을 언제 사용해야 하고 언제 멈춰야 하는지 다룹니다.
람다 함수
람다는 이름이 없는 한 줄짜리 함수입니다. 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로 전환해야 한다는 신호입니다.
lambda 키워드로 작성된 이름 없는 한 줄짜리 작은 함수입니다. 존재의 유일한 이유는 sorted()의 key=로 인라인이 되어 한 작업을 위해 별도의 함수를 정의하지 않는 것입니다. 한 깔끔한 식보다 길어지면 실제 def를 작성하고 훨씬 낫다고 느낍니다. 리스트 컴프리헨션
파이썬에서 가장 흔한 변환: 시퀀스를 받아서 각 항목에 무언가를 하고 새로운 리스트를 돌려받으세요. 리스트 컴프리헨션은 이를 한 줄의 읽기 좋은 코드로 합니다: [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] # 각각 포맷팅[expression for item in iterable]는 시퀀스를 받아 각 항목에 한 가지를 하고 새 리스트를 돌려줍니다. 왼쪽에서 오른쪽으로 읽으면 정확히 무엇을 하는지 말해줍니다. 이것이 Python을 다른 언어에서 번역하기보다 Python으로 작성한다고 느끼게 한 첫 번째 Python 기능입니다. 조건으로 필터링하기
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]if condition을 끝에 추가하여 테스트를 통과한 항목만 유지하세요: [x for x in data if x > 0]. falsy로 나오는 것은 새 리스트에서 제외됩니다. 이미 알고 있는 같은 컴프리헨션인데 그 위에 경호원이 있는 것입니다. 중첩 컴프리헨션
컴프리헨션을 중첩하여 리스트 목록을 단일 리스트로 평탄화할 수 있습니다. 왼쪽에서 오른쪽으로 읽으세요: 각 행에 대해, 그 행의 각 항목에 대해 항목을 포함하세요.
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의 각 항목에 대해 항목을 포함하세요.
중첩 컴프리헨션은 빠르게 혼동될 수 있습니다. 파싱하는 데 순간 이상 걸리면 루프를 명시적으로 작성하세요.
[item for row in matrix for item in row]. 왼쪽에서 오른쪽으로, 외부 루프 먼저, 루프를 작성하는 정확한 순서로 읽으세요. 눈에 걸리면 대신 실제 루프를 작성할 신호입니다. 딕셔너리 컴프리헨션
딕셔너리 컴프리헨션은 한 식에서 딕셔너리를 만들고, 리스트 컴프리헨션과 같은 아이디어를 사용합니다: {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 = ["사과", "바나나", "체리"]
word_lens = {word: len(word) for word in words}
# {"사과": 2, "바나나": 3, "체리": 2}{key: value for item in iterable}는 한 줄에서 딕셔너리를 만들고, key와 value 사이 콜론이 있는 리스트 컴프리헨션과 같은 모양입니다. 이미 가진 dict를 다시 모양을 내기 위해 `.items()`와 쌍으로 만들거나 두 리스트를 한 매핑으로 꿰매기 위해 `zip()`과 쌍으로 만드세요. 원하는 쌍만 유지하기 위해 끝에 `if`를 추가하세요. 세트 컴프리헨션
세트 컴프리헨션은 한 식에서 세트를 만들고, 중괄호와 콜론이 없습니다. 결과가 세트이므로 중복은 자동으로 제거됩니다.
words = ["사과", "바나나", "체리", "사과"]
unique = {w.lower() for w in words} # {"사과", "바나나", "체리"}유일한 값을 원하고 순서를 신경 쓰지 않을 때 세트 컴프리헨션을 사용하세요.
{expr for item in iterable}와 중괄호와 콜론이 없는 세트를 만들고, 세트는 중복을 무료로 버립니다. 그래서 당신의 작업이 "유일한 것들을 주세요"이면 한 줄에서 합니다. 그렇지만 어떤 특정 순서로 돌아올 것을 기대하지 마세요. 제너레이터 표현식
제너레이터는 대괄호 대신 괄호를 사용하는 리스트 컴프리헨션처럼 보입니다. 핵심 차이점은 리스트 컴프리헨션은 전체 리스트를 메모리에 한 번에 만듭니다. 제너레이터는 필요할 때만 한 번에 하나의 값을 생성합니다. 큰 시퀀스의 경우 훨씬 적은 메모리를 사용합니다.
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()를 사용하세요.
zipped 쌍 목록에서 두 개의 별도 리스트로 변환하려면 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}")
