상위 목록: 하위 목록: 작성 날짜: 읽는 데 8 분 소요

맵(map) 함수

맵(map) 함수는 반복 가능한 데이터(목록, 사전 등)의 요소를 함수에 적용해 결과를 반환하는 함수입니다.

주로 반복문을 간소화하거나, 람다(lambda) 함수에 모든 값을 적용할 때 사용합니다.

맵 함수는 결괏값 = map(적용 함수, 범위)의 형태로 사용합니다.

map을 사용하여 범위의 모든 값을 적용 함수에 대입하며, 모든 결과를 반환합니다.

Python 3에서 맵 함수는 List가 아닌 반복자(iterator)를 반환하므로, lambda 함수와 list 함수 등을 같이 사용하여 결과를 확인합니다.


제 22강의 필터 함수가 원소를 골라내는 함수라면, 맵 함수는 모든 원소를 변환하는 함수입니다. 문자열 목록을 정수 목록으로 바꾸기, 모든 가격에 세금 더하기, 모든 단어를 대문자로 바꾸기처럼 같은 처리를 모든 원소에 적용하는 작업에 사용합니다.

맵 함수도 필터 함수처럼 함수를 인수로 받는 고차 함수이며, 같은 방식의 지연 평가(lazy evaluation) 반복자를 반환합니다.

이번 강좌에서는 맵 함수의 사용법, 지연 평가로 인해 생기는 장점과 함정, 여러 범위를 함께 적용하는 방법, 그리고 리스트 간소화와 비교해 언제 무엇을 쓰는지 알아봅니다.



함수 생성

f = lambda x: x > 0
g = lambda x: x ** 2

print(list(map(f, range(-5, 5))))
print(list(map(g, range(5))))
결과
[False, False, False, False, False, False, True, True, True, True]
[0, 1, 4, 9, 16]

map(적용 함수, 범위)를 사용하여 모든 결과를 반환합니다.

list() 또는 set() 등의 구문을 추가하지 않으면 <map object at 0x000001941889FB00> 등의 iterator 형태로 출력됩니다. (메모리 주소는 실행할 때마다 달라집니다.)

f처럼 논리값을 반환하는 함수를 사용하더라도 맵 함수는 값을 걸러내지 않고, 각 원소에 대한 함수의 반환값을 그대로 반환합니다.

첫 번째 결과는 -5부터 4까지 10개의 원소가 각각 x > 0으로 변환된 값이므로 원소 개수가 그대로 10개이고, 두 번째 결과는 0부터 4까지의 원소가 각각 제곱된 값입니다.

즉, 맵 함수의 결과는 항상 입력 원소와 같은 개수이며, 각 위치의 값이 적용 함수(원소)의 반환값으로 바뀝니다.


data = ["1", "2", "3"]
result = []

for x in data:
    result.append(int(x))

print(result)
print(list(map(int, data)))
결과
[1, 2, 3]
[1, 2, 3]

반복문으로 작성한 변환과 맵 함수를 사용한 변환은 같은 결과를 반환합니다.

반복문은 빈 리스트 생성, 반복, 변환, 추가의 네 단계를 작성해야 하지만, 맵 함수는 어떤 변환을 할지(int)만 전달하면 나머지 반복은 맵 함수가 처리합니다.



반복자(iterator) 특성

m = map(str, [1, 2, 3])

print(next(m))
print(list(m))
print(list(m))
결과
1
[‘2’, ‘3’]
[]

맵 함수가 반환하는 map 객체는 반복자(iterator)이므로, 값을 미리 계산해 두지 않고 꺼낼 때마다 하나씩 계산합니다.

next() 함수로 값을 하나씩 꺼낼 수 있으며, 이미 꺼낸 값은 다시 꺼낼 수 없습니다.

그러므로, 한 번 끝까지 꺼낸 반복자를 다시 list()로 변환하면 빈 리스트가 반환됩니다.

결과를 읽어 보면, next(m)이 첫 번째 값 '1'을 꺼냈으므로 다음 list(m)에는 남은 값 '2', '3'만 들어갑니다. 마지막 list(m)은 이미 모두 꺼낸 뒤이므로 빈 리스트입니다.

결과를 여러 번 사용해야 한다면 result = list(map(...))처럼 리스트로 변환해 저장합니다.



지연 평가(lazy evaluation)

맵 함수를 호출하는 순간에는 아무 원소도 변환하지 않습니다. 값을 꺼내 달라는 요청이 올 때마다 원소 하나를 꺼내 적용 함수를 한 번 호출하고, 그 결과를 돌려준 뒤 멈춥니다.

def square(x):
    print("계산 :", x)
    return x ** 2


m = map(square, [1, 2, 3])
print("map 생성 완료")

for value in m:
    print("결과 :", value)
결과
map 생성 완료
계산 : 1
결과 : 1
계산 : 2
결과 : 4
계산 : 3
결과 : 9

map 생성 완료가 가장 먼저 출력되었으므로, 맵 객체를 만들 때는 square가 한 번도 호출되지 않았다는 것을 알 수 있습니다.

이후 for 문이 값을 하나 요청할 때마다 계산과 결과가 번갈아 출력됩니다. 모든 값을 먼저 계산한 뒤 출력하는 것이 아니라, 하나 계산하고 하나 사용하는 흐름입니다.

이 방식 덕분에 중간 결과 전체를 메모리에 저장할 필요가 없습니다.


import sys

lazy = map(lambda x: x * 2, range(1_000_000))
eager = [x * 2 for x in range(1_000_000)]

print(sys.getsizeof(lazy))
print(sys.getsizeof(eager))
결과
56
8448728

백만 개의 결과를 리스트로 만든 eager는 약 8.4MB를 차지하지만, 맵 객체 lazy는 56 바이트입니다. (64비트 Windows의 Python 3.14.6 기준이며, 리스트의 크기에는 정수 객체 자체의 크기가 포함되지 않으므로 실제 사용량은 더 큽니다.)

맵 객체는 적용 함수와 원본 반복자만 저장하고 있으므로, 원소가 아무리 많아도 크기가 변하지 않습니다.


lines = [" 10\n", "20 \n", " 30 \n"]

total = sum(map(int, map(str.strip, lines)))

print(total)
결과
60

지연 평가는 여러 단계를 연결(pipeline)할 때 특히 유용합니다.

예제는 str.strip으로 공백과 줄 바꿈을 제거하고, int로 정수로 바꾼 뒤, sum으로 합계를 구합니다.

sum이 값을 하나 요청하면 바깥 맵 객체가 안쪽 맵 객체에 값을 하나 요청하고, 안쪽 맵 객체가 lines에서 문자열을 하나 꺼내 처리합니다. 즉, 원소 하나가 모든 단계를 통과한 뒤 다음 원소가 처리되므로, 단계마다 중간 리스트가 만들어지지 않습니다.

제 25강에서 다룰 파일 객체도 한 줄씩 꺼내는 반복자이므로, sum(map(int, file))처럼 큰 파일을 한 번에 메모리에 올리지 않고 처리할 수 있습니다.



지연 평가의 주의점

m = map(print, ["A", "B"])
print("아직 출력되지 않음")

result = list(m)
print(result)
결과
아직 출력되지 않음
A
B
[None, None]

맵 함수는 값을 꺼내기 전까지 적용 함수를 호출하지 않으므로, 출력이나 파일 쓰기처럼 동작 자체가 목적인 함수를 맵 함수에 전달하면 아무 일도 일어나지 않을 수 있습니다.

예제에서 map(print, ...)를 만든 시점에는 아무것도 출력되지 않았고, list(m)으로 값을 꺼낼 때 비로소 A, B가 출력되었습니다.

또한, print()는 None을 반환하므로 결과 리스트는 [None, None]이 됩니다.

반환값이 필요 없고 동작만 수행하려면 맵 함수 대신 for 문을 사용합니다.


data = ["1", "2"]

m = map(int(), data)
print(m)

try:
    list(m)
except TypeError as e:
    print(e)
결과
<map object at 0x000001A45D75FE40>
‘int’ object is not callable

map(int(), data)처럼 함수에 괄호를 붙이면 int()가 먼저 호출되어 정수 0이 맵 함수에 전달됩니다.

맵 객체를 만드는 시점에는 적용 함수를 호출하지 않으므로 오류 없이 객체가 만들어지고, 값을 꺼내는 시점에 0을 호출하려다 TypeError가 발생합니다.

이처럼 지연 평가에서는 오류가 잘못 작성한 줄이 아닌 값을 사용하는 줄에서 발생할 수 있으므로, 오류 위치를 찾을 때 맵 객체를 만든 곳까지 거슬러 올라가 확인합니다.



형 변환

data = [-1.3, 5.5, 5.4]

f = map(int, data)

print(list(f))
결과
[-1, 5, 5]

간단한 형변환의 경우에는 map 함수를 사용한다면, 반복문 구성없이 간단하게 구현할 수 있습니다.

int()는 소수점 이하를 버리므로(0 방향으로 절사) -1.3은 -1, 5.5는 5가 됩니다.

int, float, str처럼 자료형의 이름도 값을 받아 변환된 값을 반환하는 호출 가능한 객체이므로 적용 함수로 그대로 전달할 수 있습니다.

  • Tip : list(map(int, input().split()))처럼 입력받은 문자열을 정수 목록으로 변환할 때 자주 사용합니다.


words = ["python", "map", "zip"]

print(list(map(str.upper, words)))
print(list(map(len, words)))
결과
[‘PYTHON’, ‘MAP’, ‘ZIP’]
[6, 3, 3]

문자열 메서드도 str.upper처럼 자료형.메서드 형태로 전달하면 각 원소에 메서드가 적용됩니다. str.upper("python")은 "python".upper()와 같습니다.

len처럼 원소 하나를 받는 내장 함수도 그대로 전달할 수 있습니다. 이렇게 이미 있는 함수를 적용할 때 맵 함수가 가장 간결합니다.



특정 연산 적용

def calc(x):
    return (x / 2) + 1


data = [-1.3, 5.5, 5.4]

f = map(calc, data)

print(list(f))
결과
[0.35, 3.75, 3.7]

맵(map) 함수는 논리 값과 관계 없이 모든 원소값을 함수에 적용하므로, 단순 반복 형태의 코드를 간단하게 구현할 수 있습니다.

예제는 -1.3 / 2 + 1 = 0.35, 5.5 / 2 + 1 = 3.75, 5.4 / 2 + 1 = 3.7이 차례로 계산된 결과입니다.

반면, 필터(filter) 함수는 함수의 결과가 참(True)인 원소만 남기고 값 자체는 변경하지 않습니다.

즉, 맵 함수는 모든 원소의 값을 변환하고, 필터 함수는 원소를 선택합니다.

  • Tip : 맵 함수와 필터 함수를 동시에 적용할 경우, 복잡한 형태의 구문도 간단하게 구현할 수 있습니다.


data = ["3", "-1", "x", "7"]

nums = map(int, filter(lambda s: s.lstrip("-").isdigit(), data))
print(list(nums))
결과
[3, -1, 7]

필터 함수로 정수로 바꿀 수 있는 문자열만 고른 뒤, 맵 함수로 정수로 변환합니다.

"x"를 걸러내지 않고 바로 int("x")를 적용하면 ValueError가 발생하므로, 변환 전에 걸러내는 순서가 중요합니다.

두 함수 모두 반복자를 반환하므로, 원소 하나가 필터를 통과하면 곧바로 맵으로 넘어가 변환되며 중간 리스트는 만들어지지 않습니다.



사전 적용

prices = {"apple": 1000, "pear": 2000}

print(list(map(str.upper, prices)))
print(dict(map(lambda kv: (kv[0], kv[1] * 2), prices.items())))
print({k: v * 2 for k, v in prices.items()})
결과
[‘APPLE’, ‘PEAR’]
{‘apple’: 2000, ‘pear’: 4000}
{‘apple’: 2000, ‘pear’: 4000}

사전을 맵 함수에 그대로 전달하면 키에만 함수가 적용됩니다.

값을 변환하려면 items()로 (키, 값) 쌍을 전달하고, 적용 함수가 새로운 (키, 값) 쌍을 반환하도록 작성한 뒤 dict()로 다시 사전을 만듭니다.

같은 작업은 세 번째 줄의 사전 간소화가 kv[0], kv[1] 같은 색인 없이 작성할 수 있어 더 읽기 쉽습니다.



여러 범위 적용

print(list(map(lambda x, y: x + y, [1, 2, 3], [10, 20, 30])))
print(list(map(pow, [2, 3, 4], [2, 2])))
결과
[11, 22, 33]
[4, 9]

map(적용 함수, 범위1, 범위2, ...)처럼 여러 개의 범위를 전달하면, 각 범위에서 같은 위치의 값을 하나씩 꺼내 적용 함수의 인수로 함께 전달합니다.

이때 적용 함수는 범위의 개수만큼 매개변수를 가져야 합니다.

첫 번째 줄은 1 + 10, 2 + 20, 3 + 30이 계산되며, 두 번째 줄은 pow(2, 2), pow(3, 2)가 계산됩니다.

범위의 길이가 서로 다르면 가장 짧은 범위에 맞춰 종료됩니다. 두 번째 줄의 4는 짝이 되는 값이 없으므로 오류 없이 버려집니다.


try:
    print(list(map(lambda x, y: x + y, [1, 2, 3], [10, 20], strict=True)))
except ValueError as e:
    print("ValueError :", e)
결과
ValueError : map() argument 2 is shorter than argument 1

map(적용 함수, 범위1, 범위2, ..., strict=True)를 사용하면 범위의 길이가 서로 다를 때 ValueError가 발생합니다. (Python 3.14 이상)

기본값인 strict=False에서는 짧은 쪽에 맞춰 조용히 종료되므로, 데이터가 누락되어도 알아차리기 어렵습니다. 길이가 반드시 같아야 하는 데이터를 함께 변환할 때는 strict=True를 사용합니다.

  • Tip : 같은 이름의 strict 인수는 제 24강의 zip 함수에도 있습니다. (Python 3.10 이상)


from itertools import starmap

pairs = [(2, 3), (3, 2), (10, 2)]

print(list(starmap(pow, pairs)))
print(list(map(pow, [2, 3, 10], [3, 2, 2])))
결과
[8, 9, 100]
[8, 9, 100]

인수들이 이미 (2, 3)처럼 Tuple로 묶여 있다면 itertools 모듈의 starmap() 함수를 사용합니다.

starmap(pow, pairs)는 각 Tuple을 풀어서 pow(2, 3), pow(3, 2), pow(10, 2)를 호출합니다. 여러 범위를 따로 전달하는 맵 함수와 결과가 같으며, 데이터가 어떤 형태로 준비되어 있는지에 따라 선택합니다.



간소화와 비교

print(list(map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, range(10)))))
print([x ** 2 for x in range(10) if x % 2 == 0])
결과
[0, 4, 16, 36, 64]
[0, 4, 16, 36, 64]

맵 함수와 필터 함수를 함께 사용한 구문은 [변환식 for 변수 in 범위 if 조건] 형태의 리스트 간소화(List Comprehension)로도 동일하게 구현할 수 있습니다.

lambda 함수를 새로 작성해야 한다면 간소화 구문이 더 읽기 쉬운 경우가 많으며, map(int, data)처럼 이미 정의된 함수를 그대로 적용할 때는 맵 함수가 간결합니다.

간소화 구문은 즉시 List를 생성하고, 맵 함수는 반복자를 반환하므로 값을 필요할 때마다 계산합니다.

예제의 첫 번째 줄은 괄호가 여러 겹이고 lambda가 두 번 등장하여 안쪽부터 거꾸로 읽어야 하지만, 두 번째 줄은 왼쪽에서 오른쪽으로 “짝수인 x의 제곱”이라고 바로 읽을 수 있습니다.


방식 결과 사용하기 좋은 경우
map(함수, 범위) 반복자 (지연 평가) int, str.strip처럼 이미 있는 함수를 적용할 때
[식 for x in 범위] 리스트 (즉시 계산) 변환식을 새로 작성하고, 결과를 여러 번 사용할 때
(식 for x in 범위) 반복자 (지연 평가) 변환식을 새로 작성하고, 결과를 한 번만 순서대로 사용할 때




정리

이름 설명 반환값/특징
map(함수, 범위) 모든 원소에 함수를 적용 map 반복자, 입력과 같은 개수
map(함수, 범위1, 범위2, …) 같은 위치의 원소를 함께 전달 가장 짧은 범위에서 종료
map(…, strict=True) 범위 길이가 다르면 오류 Python 3.14 이상, ValueError
itertools.starmap(함수, 범위) 묶인 인수를 풀어서 전달 반복자
list(map(…)) 결과를 리스트로 저장 여러 번 사용할 때


  • 맵 함수는 원소를 변환하고, 필터 함수는 원소를 선택합니다.
  • 맵 객체는 값을 꺼낼 때 함수를 호출하므로 메모리를 적게 쓰고 여러 단계를 연결하기 좋지만, 값을 꺼내지 않으면 함수가 실행되지 않으며 오류도 늦게 나타납니다.
  • 반복자는 한 번만 사용할 수 있으므로, 여러 번 사용할 결과는 list()로 변환해 저장합니다.

댓글 남기기