setdefault는 못생긴 dict 메서드

발행: (2026년 3월 23일 PM 07:30 GMT+9)
5 분 소요
원문: Dev.to

Source: Dev.to

Introduction

가끔씩 파이썬 퀴즈를 풀면서 실력을 유지하고 새로운 언어 기능을 발견합니다. 어느 퀴즈에서는 다음 코드의 반환값을 물었습니다.

{i: i**2 for i in range(3)}.setdefault(2, 10)

setdefault()를 한 번도 사용해 본 적이 없어서 선택지 2, 10, 4, 1 중에서 추측했죠. 정답은 4였지만, 왜 그런지 확실히 알지 못했습니다.

How setdefault() Works

파이썬 문서에는 다음과 같이 적혀 있습니다:

If key is in the dictionary, return its value. If not, insert key with a value of default and return default. default defaults to None.

위 표현을 적용하면:

>>> d = {i: i**2 for i in range(3)}   # {0: 0, 1: 1, 2: 4}
>>> d.setdefault(2, 10)
4

2가 이미 존재하므로 setdefault는 기존 값(4)을 반환하고 사전을 변경하지 않습니다.

Why the Method Is Named setdefault

메서드 이름은 수행되는 두 가지 동작을 반영합니다:

  1. Set the key to a default value if the key is missing.
  2. Return the value associated with the key (whether it was already present or just set).

.get(key, default)는 값을 단순히 조회할 뿐이지만, setdefault는 호출 후 키가 반드시 존재하도록 보장하므로, 값을 조회하고 필요 시 초기화해야 하는 코드를 간결하게 만들 수 있습니다.

Typical Use Case

리스트를 값으로 갖는 사전을 만들 때 흔히 쓰이는 패턴입니다:

items_by_color = {}
items = [('duck', 'purple'), ('water bottle', 'purple'), ('uni-duck', 'pink')]

for item_name, item_color in items:
    # Get the list for the colour, or set it to a new empty list and get that
    items_list = items_by_color.setdefault(item_color, [])
    items_list.append(item_name)

# Result: {'purple': ['duck', 'water bottle'], 'pink': ['uni-duck']}

여기서 setdefault는 리스트에 항목을 추가하기 전에 명시적인 if … else 검사를 할 필요를 없애 줍니다.

Performance Considerations

setdefault를 사용한 경우와 명시적인 if … else 블록을 사용한 경우의 성능을 비교해 보았습니다.

def test_no_set_default(sequence: dict) -> dict:
    """Re‑order incoming dict by value using if..else."""
    reversed = {}
    for k, v in sequence.items():
        if v not in reversed:
            reversed[v] = [k]
        else:
            reversed[v].append(k)
    return reversed


def test_set_default(sequence: dict) -> dict:
    """Re‑order incoming dict by value using setdefault."""
    reversed = {}
    for k, v in sequence.items():
        reversed.setdefault(v, [])
        reversed[v].append(k)
    return reversed

무작위 데이터를 생성하는 도우미 함수:

from faker import Faker

def make_dict(items: int = 1000) -> dict:
    """Create a dictionary of random values."""
    fake = Faker()
    return {fake.name(): fake.color_name() for _ in range(items)}

두 함수를 타이밍해 보기:

from timeit import timeit

def test_calls(times: int = 1_000_000):
    setup_dict = make_dict()
    t1 = timeit(lambda: test_no_set_default(setup_dict), number=times)
    t2 = timeit(lambda: test_set_default(setup_dict), number=times)
    print(f"no   setdefault: {t1}")
    print(f"with setdefault: {t2}")

샘플 출력:

$ uv run python3 set_default_comparison.py --times 1000000
no   setdefault: 100.65228875500907
with setdefault: 136.7275900120003

이 마이크로‑벤치마크에서는 setdefault 버전이 약 30 % 정도의 오버헤드를 보였습니다. 많은 스크립트에서는 차이가 미미할 수 있지만, 성능이 중요한 코드에서는 영향을 줄 수 있습니다.

Conclusion

setdefault는 누락된 키를 초기화하고 그 값을 반환하는 동작을 하나로 결합해 코드를 더 간결하고 표현력 있게 만들 수 있습니다. 하지만 명시적인 if … else 검사에 비해 약간의 실행 시간 비용이 발생합니다. 가독성과 간결성이 약간의 성능 저하보다 중요할 때 사용하고, 마이크로초 단위까지 최적화가 필요한 핫 경로에서는 명시적인 패턴을 선호하세요.

0 조회
Back to Blog

관련 글

더 보기 »