람다식은 언제 사용할까? 일반 함수와의 차이와 활용 사례

람다식이란? 함수를 값처럼 전달하는 방법#

람다식은 이름을 따로 붙이지 않고 함수를 만드는 표현식입니다. 특히 어떤 함수에 “이 데이터를 어떻게 처리할지” 알려주기 위해 짧은 동작을 바로 전달할 때 유용합니다.

예를 들어 학생 목록을 정렬한다고 해보겠습니다. 이름순으로 정렬할 수도 있고 성적순으로 정렬할 수도 있습니다. 목록과 정렬 함수는 그대로 두고, 무엇을 비교 기준으로 삼을지 결정하는 함수만 전달하면 됩니다.

students = [
    ("김철수", 88),
    ("이영희", 95),
    ("박지성", 76),
]

sorted_students = sorted(students, key=lambda student: student[1])

print(sorted_students)
# [('박지성', 76), ('김철수', 88), ('이영희', 95)]

lambda student: student[1]은 학생 데이터 하나를 받아 성적을 반환하는 작은 함수입니다. sorted()는 그 반환값을 정렬 기준으로 사용합니다.

람다식을 이해하려면 문법에 앞서 함수를 전달할 수 있다는 개념부터 살펴봐야 합니다. 이 글에서는 Python을 예제 언어로 사용하지만, 동작을 다른 코드에 전달한다는 생각은 여러 언어에 공통으로 적용됩니다.

함수를 값처럼 다룬다는 것은 무슨 뜻일까?#

프로그래밍에서 숫자나 문자열은 변수에 저장하고, 함수에 전달하고, 다른 함수에서 반환할 수 있습니다.

price = 10_000

def get_price():
    return price

print(get_price())  # 10000

Python에서는 함수도 변수에 담고, 다른 함수에 전달하고, 함수의 결과로 반환할 수 있습니다. 이런 성질을 설명할 때 함수를 일급 값 또는 일급 객체처럼 다룬다고 말합니다.

함수에 이름을 하나 더 붙이기#

def say_hello(name):
    return f"안녕하세요, {name}님!"


greet = say_hello

print(greet("홍길동"))
# 안녕하세요, 홍길동님!

greet = say_hello는 함수의 실행 결과를 저장하는 코드가 아닙니다. say_hello라는 함수를 greet라는 이름으로도 참조하게 합니다.

괄호의 유무를 비교해 보세요.

greet = say_hello           # 함수를 전달
message = say_hello("홍길동")  # 함수를 호출하고 결과 문자열을 저장
  • say_hello는 함수 자체를 가리킵니다.
  • say_hello("홍길동")은 함수를 지금 실행한 결과입니다.

람다식을 사용할 때도 이 구분이 중요합니다. 다른 함수에 동작을 맡기려는 상황이라면 함수를 먼저 실행해 결과를 넘기는 것이 아니라, 실행할 함수를 전달해야 합니다.

함수를 다른 함수의 인자로 전달하기#

def say_hello(name):
    return f"안녕하세요, {name}님!"


def welcome_user(name, greeting_function):
    message = greeting_function(name)
    print(message)


welcome_user("홍길동", say_hello)

실행 결과:

안녕하세요, 홍길동님!

welcome_user()는 인사말을 만드는 구체적인 방법을 모릅니다. 전달받은 greeting_function을 호출할 뿐입니다.

다른 인사 함수를 전달하면 welcome_user()를 고치지 않고 동작을 바꿀 수 있습니다.

def say_good_morning(name):
    return f"좋은 아침입니다, {name}님!"


welcome_user("홍길동", say_good_morning)

이처럼 함수를 인자로 받거나 함수를 반환하는 함수를 고차 함수라고 합니다. sorted()도 key에 함수를 받을 수 있으므로 고차 함수의 예입니다.

람다식은 어떻게 작성할까?#

Python의 람다식 기본 형태는 다음과 같습니다.

lambda 매개변수: 표현식

예를 들어 두 수를 더하는 함수는 일반 함수와 람다식으로 각각 작성할 수 있습니다.

def add(x, y):
    return x + y


lambda x, y: x + y

두 함수 모두 x와 y를 받아 더한 값을 반환합니다. 람다식에서는 return을 적지 않습니다. 콜론 뒤 표현식을 계산한 결과가 자동으로 반환됩니다.

람다식도 변수에 담아 호출할 수 있습니다.

add_numbers = lambda x, y: x + y

print(add_numbers(10, 20))  # 30

하지만 이미 이름을 붙여 재사용할 생각이라면 다음처럼 def로 작성하는 편이 의도가 더 분명한 경우가 많습니다.

def add_numbers(x, y):
    return x + y

람다식이 특히 편리한 곳은 다른 함수가 짧은 함수를 인자로 요구하는 자리입니다.

앞의 인사 예제를 람다식으로 바꾸기#

def welcome_user(name, greeting_function):
    message = greeting_function(name)
    print(message)


welcome_user(
    "홍길동",
    lambda name: f"어서 오세요, {name}님!"
)

실행 결과:

어서 오세요, 홍길동님!

인사말을 만드는 짧은 함수를 별도로 정의하지 않고 welcome_user()를 호출하는 자리에서 전달했습니다. welcome_user() 안에서는 전달받은 함수를 평소와 똑같이 호출합니다.

람다식의 대표적인 활용: 정렬 기준 전달하기#

학생 이름과 성적을 함께 관리하는 목록을 보겠습니다.

students = [
    ("김철수", 88),
    ("이영희", 95),
    ("박지성", 76),
    ("손흥민", 92),
]

각 항목은 (이름, 성적) 형태의 튜플입니다. 기본 정렬을 하면 튜플의 첫 번째 값인 이름이 먼저 비교됩니다.

print(sorted(students))
# [('김철수', 88), ('박지성', 76), ('손흥민', 92), ('이영희', 95)]

성적순으로 정렬하려면 sorted()의 key에 각 학생 데이터에서 성적을 꺼내는 함수를 전달합니다.

일반 함수로 정렬 기준 만들기#

def get_score(student):
    return student[1]


sorted_by_score = sorted(students, key=get_score)

print(sorted_by_score)
# [('박지성', 76), ('김철수', 88), ('손흥민', 92), ('이영희', 95)]

sorted()는 각 항목을 get_score()에 넣어 정렬에 사용할 값을 얻습니다.

학생 데이터 get_score()의 반환값
("김철수", 88) 88
("이영희", 95) 95
("박지성", 76) 76
("손흥민", 92) 92

여기서 get_score()가 오직 이 정렬에서만 쓰이고 내용도 짧다면, 정렬하는 자리에 람다식으로 적을 수 있습니다.

sorted_by_score = sorted(
    students,
    key=lambda student: student[1]
)

sorted()의 key에는 성적 숫자 자체를 하나 전달하는 것이 아닙니다. 각 항목에서 정렬 기준을 계산할 함수를 전달합니다.

내림차순으로 정렬하기#

성적이 높은 학생부터 보려면 reverse=True를 사용합니다.

high_scores_first = sorted(
    students,
    key=lambda student: student[1],
    reverse=True
)

print(high_scores_first)
# [('이영희', 95), ('손흥민', 92), ('김철수', 88), ('박지성', 76)]

람다식은 “성적을 기준으로 삼는다”는 역할을 하고, reverse=True는 정렬 방향을 결정합니다. 서로 맡은 일이 다릅니다.

기준이 두 개라면?#

성적이 같은 학생끼리는 이름순으로 정렬하고 싶을 수도 있습니다. key 함수가 튜플을 반환하게 하면 됩니다.

students = [
    ("김철수", 90),
    ("이영희", 95),
    ("박지성", 90),
]

result = sorted(
    students,
    key=lambda student: (student[1], student[0])
)

print(result)
# [('김철수', 90), ('박지성', 90), ('이영희', 95)]

성적을 먼저 비교하고, 성적이 같을 때 이름을 비교합니다. 다만 정렬 규칙이 더 길어지거나 별도 설명이 필요해진다면 이름 있는 함수로 분리하는 편이 읽기 쉽습니다.

map(): 각 데이터에 같은 변환 적용하기#

map()은 데이터를 하나씩 함수에 전달해 변환합니다.

numbers = [1, 2, 3, 4, 5]

squared = map(lambda number: number * number, numbers)

print(list(squared))
# [1, 4, 9, 16, 25]

lambda number: number * number는 숫자 하나를 받아 제곱을 반환합니다. map()은 목록의 각 숫자에 이 함수를 적용합니다.

여기서 Python의 map()은 즉시 완성된 리스트를 반환하지 않고 반복 가능한 map 객체를 반환합니다. 그래서 결과를 리스트로 보고 싶을 때 list()로 감쌌습니다.

같은 작업을 for문으로 작성하면 다음과 같습니다.

numbers = [1, 2, 3, 4, 5]
squared = []

for number in numbers:
    squared.append(number * number)

print(squared)
# [1, 4, 9, 16, 25]

Python에서는 리스트 컴프리헨션도 자주 사용합니다.

squared = [number * number for number in numbers]

세 방식 모두 사용할 수 있습니다. map()과 람다식을 쓴 코드가 언제나 더 읽기 쉬운 것은 아닙니다. 이 예제에서는 리스트 컴프리헨션이 더 자연스럽다고 느낄 수도 있습니다.

filter(): 조건에 맞는 데이터만 고르기#

filter()는 각 항목에 함수를 적용하고, 그 결과가 참으로 판단되는 항목만 남깁니다.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

even_numbers = filter(
    lambda number: number % 2 == 0,
    numbers
)

print(list(even_numbers))
# [2, 4, 6, 8, 10]

람다식은 숫자가 짝수라면 True, 홀수라면 False를 반환합니다. filter()는 원래 숫자 가운데 조건을 통과한 값만 내보냅니다.

map()과 차이를 표로 정리하면 다음과 같습니다.

함수 전달한 함수가 할 일 결과
map() 각 항목을 어떤 값으로 바꿀지 계산 변환된 값들
filter() 각 항목을 남길지 판단 조건을 통과한 원래 항목들

Python의 filter()도 리스트가 아닌 반복 가능한 객체를 반환합니다. 결과를 목록으로 확인하려면 list()로 변환할 수 있습니다.

같은 작업을 리스트 컴프리헨션으로 작성할 수도 있습니다.

even_numbers = [number for number in numbers if number % 2 == 0]

람다식은 문제를 해결하는 여러 표현 방법 중 하나입니다. 코드가 짧다는 이유만으로 늘 우선할 필요는 없습니다.

map()과 filter()를 함께 사용하기#

1부터 10까지 숫자에서 짝수를 고른 뒤 제곱한다고 해보겠습니다.

numbers = range(1, 11)

even_numbers = filter(
    lambda number: number % 2 == 0,
    numbers
)

squared_even_numbers = map(
    lambda number: number * number,
    even_numbers
)

print(list(squared_even_numbers))
# [4, 16, 36, 64, 100]

처리 흐름은 다음과 같습니다.

  1. filter()가 짝수 2, 4, 6, 8, 10을 선택합니다.
  2. map()이 각각을 제곱합니다.
  3. list()가 결과를 목록으로 만듭니다.

합계가 목적이라면 sum()에 결과를 전달할 수도 있습니다.

numbers = range(1, 11)

result = sum(
    map(
        lambda number: number * number,
        filter(lambda number: number % 2 == 0, numbers)
    )
)

print(result)  # 220

다만 안쪽부터 읽어야 하는 함수 호출이 깊어지면 입문자에게 흐름이 잘 보이지 않을 수 있습니다. Python에서는 다음 표현이 더 읽기 쉬울 때도 많습니다.

result = sum(
    number * number
    for number in range(1, 11)
    if number % 2 == 0
)

print(result)  # 220

핵심은 map()·filter()를 반드시 쓰는 것이 아닙니다. 동작을 함수로 전달할 수 있다는 원리를 이해하고, 읽는 사람이 처리 흐름을 따라갈 수 있는 형태를 선택하는 것입니다.

함수를 반환할 수도 있다: 동작을 미리 설정하기#

함수를 값처럼 다룰 수 있다면, 한 함수가 다른 함수를 만들어 반환할 수도 있습니다.

def make_multiplier(factor):
    def multiply(number):
        return number * factor

    return multiply


double = make_multiplier(2)
triple = make_multiplier(3)

print(double(10))  # 20
print(triple(10))  # 30

make_multiplier(2)는 곱셈 결과를 곧바로 반환하지 않습니다. 나중에 숫자를 받아 2배로 만드는 함수를 반환합니다. 반환된 함수는 자신이 만들어질 때 받은 factor 값을 사용합니다.

여기서 짧은 동작이라면 람다식으로도 표현할 수 있습니다.

def make_multiplier(factor):
    return lambda number: number * factor

이 예제는 람다식이 단지 sorted()용 문법이 아니라, 필요한 동작을 하나의 값처럼 만들 수 있는 표현식이라는 점을 보여줍니다.

람다식은 언제 쓰고, 언제 일반 함수를 쓸까?#

람다식이 잘 맞는 경우#

  • 동작이 하나의 짧은 표현식으로 끝납니다.
  • 그 자리에서 쓰는 목적이 분명합니다.
  • 함수의 이름을 따로 붙이지 않아도 의미를 이해할 수 있습니다.
products = [
    {"name": "노트", "price": 3_000},
    {"name": "키보드", "price": 25_000},
]

result = sorted(
    products,
    key=lambda product: product["price"]
)

key=lambda product: product["price"]는 가격순 정렬이라는 의도가 바로 보입니다.

이름 있는 함수가 나은 경우#

규칙을 설명해야 하거나 여러 단계의 처리가 필요하다면 def를 사용합니다.

def calculate_shipping_priority(order):
    if order["is_express"]:
        return 0

    if order["is_member"]:
        return 1

    return 2


sorted_orders = sorted(
    orders,
    key=calculate_shipping_priority
)

이 코드는 orders 목록이 준비되어 있다고 가정한 예입니다. 배송 우선순위 규칙을 lambda 하나에 억지로 넣는 것보다 함수 이름과 여러 줄의 조건문으로 드러내는 편이 이해하기 쉽습니다.

함수를 여러 곳에서 재사용하거나 독립적으로 확인해야 할 때도 이름을 붙이는 편이 좋습니다.

판단 기준: 람다식으로 줄인 결과, 읽는 사람이 “이 함수가 무엇을 하는가?”를 바로 알 수 있는가?

Python 람다식의 문법상 범위#

Python의 람다식은 콜론 뒤에 하나의 표현식을 적습니다.

lambda number: number * 2

여러 줄의 명령문을 차례로 작성하는 일반 함수와 같은 방식으로 쓰지는 못합니다. 조건 표현식 자체는 넣을 수 있지만, 복잡하게 중첩하면 읽기 어려워집니다.

# 문법적으로 가능하지만 규칙이 길어지면 일반 함수가 더 읽기 쉽다.
lambda score: "통과" if score >= 60 else "재학습"

람다식이 곧 함수형 프로그래밍일까?#

람다식은 함수를 간단히 작성하는 문법입니다. 람다식을 썼다는 사실만으로 코드가 자동으로 순수 함수가 되거나, 데이터 변경이 사라지거나, 함수형 프로그래밍의 원칙을 모두 따른 것은 아닙니다.

예를 들어 다음 람다식은 외부 목록을 변경합니다.

results = []

record = lambda value: results.append(value)

record(10)
print(results)  # [10]

짧은 익명 함수지만 바깥의 results를 바꾸는 부수 효과가 있습니다. 함수형 프로그래밍의 순수 함수나 불변성은 람다식이라는 문법과 별개로 판단해야 합니다.

람다식을 배우면서 가장 먼저 익혀야 할 것은 “동작을 작성해 다른 곳에 전달할 수 있다”는 점입니다. 데이터의 불변성과 부수 효과는 그다음에 살펴볼 설계 주제입니다.

처음 배울 때 자주 하는 실수#

함수를 전달해야 하는데 먼저 호출하기#

def get_score(student):
    return student[1]


# 잘못된 사용: get_score()를 호출할 학생 데이터가 없다.
# sorted(students, key=get_score())

key에는 나중에 각 학생에게 적용할 함수를 전달해야 합니다.

sorted(students, key=get_score)

람다식을 쓸 때도 마찬가지입니다.

sorted(students, key=lambda student: student[1])

람다식에 이름을 붙이면 항상 더 좋은가?#

get_score = lambda student: student[1]

이 코드가 틀린 것은 아닙니다. 하지만 다른 곳에서도 쓰고 이름이 필요하다면 다음처럼 일반 함수로 작성하는 편이 명확할 수 있습니다.

def get_score(student):
    return student[1]

람다식의 장점은 단순히 def보다 글자 수가 적다는 데 있지 않습니다. 사용하는 위치에서 짧은 동작을 바로 전달할 수 있다는 데 있습니다.

map()과 filter()의 결과를 리스트라고 생각하기#

numbers = [1, 2, 3]

result = map(lambda number: number * 2, numbers)
print(result)

print(result)는 [2, 4, 6]을 바로 출력하지 않습니다. Python에서 map()은 반복 가능한 map 객체를 반환합니다.

print(list(result))  # [2, 4, 6]

한 번 순회한 반복 가능 객체를 다시 사용하려 할 때 기대와 다른 결과가 나올 수 있으므로, 결과를 여러 번 사용할 필요가 있다면 리스트로 만드는 방법도 검토해야 합니다.

직접 확인해 보기#

다음 코드를 실행하기 전에 정렬 결과를 예상해 보세요.

books = [
    {"title": "자료구조 입문", "pages": 320},
    {"title": "프로그래밍 첫걸음", "pages": 180},
    {"title": "알고리즘 기초", "pages": 240},
]

short_books_first = sorted(
    books,
    key=lambda book: book["pages"]
)

for book in short_books_first:
    print(book["title"])

실행 결과:

프로그래밍 첫걸음
알고리즘 기초
자료구조 입문

lambda book: book["pages"]는 책 한 권의 페이지 수를 반환합니다. sorted()는 이 값을 기준으로 책을 배치합니다.

이번에는 책 제목의 길이가 짧은 순서로 정렬해 보세요. 바꿔야 할 부분은 sorted()에 전달하는 정렬 기준 함수입니다.

short_titles_first = sorted(
    books,
    key=lambda book: len(book["title"])
)

이 연습을 통해 데이터를 처리하는 함수는 그대로 두고, 처리 기준이 되는 동작을 전달해 결과를 바꿀 수 있다는 점을 확인할 수 있습니다.

정리#

람다식은 이름 없는 짧은 함수를 만드는 표현식입니다. Python에서는 함수를 값처럼 다룰 수 있으므로, 람다식으로 만든 함수를 sorted()의 정렬 기준이나 다른 함수의 처리 규칙으로 전달할 수 있습니다.

  • 함수 이름 뒤에 괄호를 붙이면 호출하고, 붙이지 않으면 함수 자체를 전달할 수 있습니다.
  • lambda 매개변수: 표현식은 표현식의 결과를 반환합니다.
  • sorted()의 key, map(), filter()처럼 함수를 인자로 받는 곳에서 활용할 수 있습니다.
  • 여러 단계의 규칙이나 반복해서 사용할 동작은 이름 있는 일반 함수가 더 읽기 쉬울 수 있습니다.

람다식의 목표는 코드를 무조건 한 줄로 줄이는 것이 아닙니다. 데이터와 함께 처리 방법도 전달할 수 있다는 생각을 익히고, 그 방법이 코드의 뜻을 더 분명하게 보여줄 때 사용하는 것입니다.