|

파이썬 map과 filter, list comprehension 차이: 언제 무엇이 더 읽기 쉬울까

파이썬 map과 filter, list comprehension 차이를 비교하는 대표 이미지
무엇이 더 짧은가보다 무엇이 더 읽기 쉬운가가 중요하다

파이썬 map과 filter, list comprehension 차이는 문법 자체보다 읽기 쉬움에서 갈리는 경우가 많습니다. 같은 일을 하더라도 어떤 표현은 의도가 바로 보이고, 어떤 표현은 한 번 더 해석해야 이해됩니다.

이 글에서는 파이썬 map과 filter, list comprehension 차이를 정의 암기보다 실무에서 무엇이 더 읽기 쉬운가라는 기준으로 정리합니다. 핵심은 짧아 보이는 문법이 아니라, 코드를 처음 보는 사람이 의도를 얼마나 빨리 읽을 수 있느냐입니다.


먼저 결론

  • 결과를 리스트로 만들면서 변환이나 필터링 의도를 한눈에 보여주고 싶다면 list comprehension이 자주 유리하다
  • 이미 있는 이름 있는 함수를 그대로 적용하는 파이프라인이라면 map이나 filter도 충분히 읽기 쉽다
  • lambda가 길어지기 시작하면 map, filter는 금방 읽기 어려워진다
  • list comprehension도 중첩과 조건이 많아지면 똑똑해 보이기만 하고 실제로는 읽기 힘들어진다

짧은 문법이 좋은 코드가 아니라, 의도가 바로 보이는 문법이 좋은 코드입니다.


파이썬 map과 filter, list comprehension 차이 한눈에 보기

map

map은 함수와 iterable을 받아 각 항목에 함수를 적용하는 iterator를 만듭니다. 결과가 바로 리스트로 나오는 것이 아니라 필요할 때 꺼내 쓰는 흐름입니다.

numbers = [1, 2, 3, 4]
result = map(str, numbers)
print(result)
print(list(result))
<map object at 0x...>
['1', '2', '3', '4']

filter

filter는 조건 함수가 참이라고 판단한 항목만 남기는 iterator를 만듭니다. function 자리에 None을 넣으면 truthy 값만 남깁니다.

values = [0, 1, '', 'hello', None, True]
result = filter(None, values)
print(list(result))
[1, 'hello', True]

list comprehension

list comprehensionforif를 이용해 리스트를 만드는 문법입니다. PEP 202도 map, filter, 중첩 루프가 쓰이던 상황을 더 간결하게 만들기 위한 문법으로 설명합니다.

numbers = [1, 2, 3, 4]
result = [str(n) for n in numbers]
print(result)
['1', '2', '3', '4']

여기서 중요한 차이는 하나입니다. map, filter는 iterator를 만들고, list comprehension은 list를 만듭니다. 하지만 실무에서는 이 차이만큼이나 읽는 사람이 의도를 얼마나 빨리 파악하느냐도 중요합니다.


단순 변환은 무엇이 더 읽기 쉬울까

예를 들어 이름 목록을 모두 대문자로 바꾸고 싶다면, comprehension은 변환 흐름이 왼쪽에서 오른쪽으로 자연스럽게 보입니다. 반면 map은 함수 호출을 한 번 해석하고 그 안의 함수와 iterable을 다시 매칭해야 합니다. 그래서 짧은 일회성 변환에서는 comprehension이 더 직관적으로 느껴지는 경우가 많습니다.

names = ['alice', 'bob', 'charlie']
upper_names = [name.upper() for name in names]
names = ['alice', 'bob', 'charlie']
upper_names = list(map(str.upper, names))

다만 이미 있는 이름 있는 함수를 그대로 적용할 때는 map도 충분히 깔끔합니다. 예를 들어 normalize_name 같은 함수가 있다면 list(map(normalize_name, names))는 의도가 꽤 선명합니다. 즉, 이름 있는 함수 + 일괄 적용 패턴에서는 map도 좋은 선택입니다.


lambda가 들어가면 왜 금방 무거워질까

문제는 lambda가 붙는 순간 자주 시작됩니다. map(lambda name: name.strip().title(), names) 같은 코드는 틀리지 않지만, 읽는 사람은 map을 해석하고 lambda 본문을 다시 읽어야 합니다. 같은 일을 comprehension으로 쓰면 보통 한 번에 더 빨리 읽힙니다.

names = ['  alice', 'BOB  ', ' charlie ']
normalized = list(map(lambda name: name.strip().title(), names))
names = ['  alice', 'BOB  ', ' charlie ']
normalized = [name.strip().title() for name in names]

lambda가 길어질수록 map과 filter는 읽는 사람이 함수 호출을 한 번 더 해석해야 해서 가독성 비용이 커집니다.


단순 필터링은 무엇이 더 자연스러울까

빈 문자열을 제거하는 예시에서는 comprehension이 더 빨리 읽히는 경우가 많습니다. 무엇을 순회하고, 무엇을 남기는지가 코드 구조에 직접 보이기 때문입니다. 반대로 filter(lambda ...)는 조건식이 함수 안으로 들어가 있어서 한 번 더 따라가야 합니다.

words = ['python', '', 'map', ' ', 'filter']
filtered = list(filter(lambda word: word.strip(), words))
words = ['python', '', 'map', ' ', 'filter']
filtered = [word for word in words if word.strip()]

하지만 filter도 이름 있는 판별 함수가 있을 때는 충분히 괜찮습니다. 예를 들어 is_non_empty_word 같은 함수가 있다면 filter(is_non_empty_word, words)는 남기는 기준이 또렷합니다. 그래서 filter재사용 가능한 predicate 함수가 이미 있을 때 자연스럽습니다.


변환과 필터링이 같이 나오면 comprehension이 자주 강해진다

실무에서는 변환만, 필터링만이 아니라 둘이 같이 붙는 경우가 더 많습니다. 이때는 조건과 변환이 한 흐름 안에서 같이 보이는 comprehension이 자주 더 읽기 쉽습니다.

words = ['  Python  ', '', ' MAP ', 'Filter ', '   ']
cleaned = list(
    map(
        lambda word: word.strip().lower(),
        filter(lambda word: word.strip(), words)
    )
)
words = ['  Python  ', '', ' MAP ', 'Filter ', '   ']
cleaned = [word.strip().lower() for word in words if word.strip()]

이 예시에서는 map(filter(...)) 구조를 안에서 바깥으로 해석해야 하고 lambda도 두 번 나옵니다. 반면 comprehension은 words를 돌고, 조건에 맞는 값만 남기고, 변환해서 리스트로 만든다는 흐름이 한 줄에 바로 드러납니다.


그렇다고 comprehension이 항상 정답은 아니다

여기서 많이 하는 실수는 map, filter보다 comprehension이 읽기 쉽다고 느낀 뒤 모든 로직을 comprehension 한 줄에 밀어 넣는 것입니다. 중첩 루프와 여러 조건이 겹치면 문법적으로 맞아도 한눈에 읽기 쉽다고 보기는 어렵습니다.

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = [n * 2 for row in matrix for n in row if n % 2 == 1 if n > 3]

이런 경우에는 차라리 명시적인 for문이 더 낫습니다. 판단 단계를 분리하면 줄 수는 늘어도 읽기 비용은 오히려 줄어듭니다. 즉, comprehension도 조건이 많아지고 중첩이 깊어지면 더 이상 좋은 축약이 아닙니다.

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
result = []

for row in matrix:
    for n in row:
        if n % 2 == 0:
            continue
        if n <= 3:
            continue
        result.append(n * 2)

언제 무엇을 고르면 좋을까

  • 리스트를 만들고 싶고, 순회 대상과 조건이 단순하다면 list comprehension을 먼저 떠올리기
  • 이미 있는 이름 있는 함수를 전체에 적용한다면 map도 좋다
  • 이미 있는 이름 있는 조건 함수로 거른다면 filter도 좋다
  • 중첩이 깊고 조건이 많다면 한 줄 욕심을 버리고 for문으로 풀기

코드 리뷰 기준으로는 짧은가보다 처음 보는 사람이 의도를 바로 읽는가가 더 중요합니다.


자주 하는 오해

  1. map과 filter는 오래된 스타일이라 쓰면 안 된다는 오해
  2. comprehension은 한 줄이면 무조건 좋다는 오해
  3. iterator와 list 차이를 무시해도 된다는 오해

도구 자체를 선악으로 나누기보다, 지금 코드의 의도가 어디서 가장 덜 숨는지를 보는 편이 훨씬 실무적입니다.


마무리

파이썬 map과 filter, list comprehension 차이를 실무적으로 보면 답은 단순합니다. 무엇이 더 파이썬스럽냐보다 지금 이 코드에서 의도가 가장 잘 드러나는 표현이 무엇인가를 먼저 보면 됩니다. 대부분의 일회성 변환과 필터링에서는 comprehension이 더 읽기 쉬운 경우가 많지만, 이름 있는 함수를 적용하는 흐름에서는 map, filter도 충분히 좋은 선택입니다. 반대로 comprehension이 너무 복잡해지면 그 순간부터는 축약이 아니라 숨기기가 됩니다.

같이 읽으면 좋은 글로는 파이썬 pass, continue, break 차이, 파이썬 딕셔너리와 집합 차이가 있습니다. 공식 기준은 Python map 문서, filter 문서, list comprehensions 튜토리얼, PEP 202를 참고하면 됩니다.

함께보면 좋은 글