Logo

파이썬 사전의 기본값 처리 (collections.defaultdict)

파이썬의 내장 자료구조인 사전(dictionary)를 사용하다 보면 어떤 키(key)에 대한 값(value)이 없는 경우에 대한 처리를 해야 할 때가 많죠? 이번 포스팅에서는 이러한 경우 일반적으로 어떻게 처리하는지 살펴보고, 관련해서 파이썬에서 제공하는 몇 가지 접근법에 대해서 알아보도록 하겠습니다.

일반적인 사전 기본값 처리

그럼 파이썬에서 사전을 다룰 때 어떤 경우에 기본값 처리가 필요한지 간단한 실습을 통해 알아보겠습니다. 단어가 주어졌을 때 각 알파벳에 대한 글자의 수를 세어서 사전에 저장해주는 함수를 작성해볼께요.

def count_letters(word):
    counter = {}
    for letter in word:
        if letter not in counter:
            counter[letter] = 0
        counter[letter] += 1
    return counter

그럼 이 함수를 호출해볼까요?

count_letters("banana") # {'b': 1, 'a': 3, 'n': 2}

for 루프 안에 if 조건절을 통해서 counter 사전에 어떤 글자가 키(key)로 존재하지 않는 경우, 해당 키에 대한 기본값을 0으로 주고 있는데요. 이러한 코딩 패턴은 파이썬에서 사전을 사용할 때 상당히 자주 접할 수 있는데, 코드를 읽을 때 주요 흐름의 파악을 방해하는 요소가 되기도 합니다.

dict.setdefault

사전에 기본값을 줄 때 if 조건절을 피할 수 있도록 파이썬의 사전(dictionary) 자료구조는 setdefault() 함수를 제공합니다. 첫번째 인자로 키(key)값, 두번째 인자로 기본값(default value)를 넘기면 되는데요.

def count_letters(word):
    counter = {}
    for letter in word:
        counter.setdefault(letter, 0)
        counter[letter] += 1
    return counter

호출하면 동일한 결과를 얻을 수 있습니다.

count_letters("banana") # {'b': 1, 'a': 3, 'n': 2}

다만 코드가 깔끔해져서 좋기는데, for 루프 내에서 setdefault() 함수가 무조건적으로 항상 호출되는 부분이 좀 마음에 들지 않습니다.

dict.get

사전에 저장된 값을 접근할 때, 대괄호 대신에 get() 함수를 사용하면 두 번째 인자로 기본값을 명시해줄 수 있는데요. 이 기능을 이용하면 if 조건절 없이도 사전의 기본값을 지정해줄 수 있습니다.

def count_letters(word):
    counter = {}
    for letter in word:
        counter[letter] = counter.get(letter, 0) + 1
    return counter

호출하면 역시 동일한 결과를 얻을 수 있습니다.

count_letters("banana") # {'b': 1, 'a': 3, 'n': 2}

이 방법이 위 방법보다는 살짝 더 나은 것 같지만 좀 더 나은 방법은 없을까요?

collections.defaultdict

이렇게 사전에 기본값을 설정해줘야 할 때는 파이썬의 내장 모듈인 collectionsdefaultdict 클래스를 사용하면 딱 인데요. defaultdict 클래스의 생성자로 기본값을 생성해주는 함수를 넘기면, 모든 키에 대해서 값이 없는 경우 자동으로 생성자의 인자로 넘어온 함수를 호출하여 그 결과값으로 설정해줍니다.

그럼 먼저, collections 모듈의 defaultdict 클래스는 다음과 같이 불러와야 합니다.

from collections import defaultdict

이제, 위에서 작성한 코드를 임포트한 defaultdict를 이용해서 개선하면, for 루프로 부터 사전의 기본값 처리 코드를 완전히 제거할 수가 있습니다.

from collections import defaultdict

def count_letters(word):
    counter = defaultdict(int)
    for letter in word:
        counter[letter] += 1
    return counter

defaultdict로 생성한 사전은 문자열로 출력했을 때 살짝 다르게 나오지만 키를 통해 값을 접근해보면 동일하게 나옵니다.

count_letters("banana") # defaultdict(int, {'b': 1, 'a': 3, 'n': 2})
count_letters("banana")["a"] # 3

여기서 defaultdict 클래스의 생성자로 int 함수를 넘긴 이유는 int()0을 리턴하기 때문입니다. 람다 함수를 활용해서 다음과 같이 int 함수 대신에 lambda: 0를 넘겨도 동일하게 작동을 합니다.

from collections import defaultdict

def count_letters(word):
    counter = defaultdict(lambda: 0)
    for letter in word:
        counter[letter] += 1
    return counter

사전 기본값으로 빈 리스트 설정

collections.defaultdict를 활용할 수 있는 다른 사례로 데이터를 특정 기준에 의해 카테고리로 묶는 경우를 들 수 있습니다.

예를 들어, 여러 단어가 주어졌을 때 이 단어들을 길이에 따라 분류해주는 코드를 한번 작성해볼께요.

from collections import defaultdict

def group_words(words):
    grouper = defaultdict(list)
    for word in words:
        length = len(word)
        grouper[length].append(word)
    return grouper

이번에는 defaultdict 생성자에 list 함수를 넘겼기 때문에, grouper 사전에 어떤 글자가 키(key)로 존재하지 않는 경우, 해당 키에 대한 기본값을 비어있는 리스트(empty list)로 설정해줍니다.

여러 개의 단어를 인자로 넘겨서 함수를 호출해보면 다음과 같이 단어 길이에 따라 단어가 분류됩니다.

group_words(["banana", "strawberry", "mango", "pineapple", "watermelon", "blueberry", "kiwi", "grapefruit"])
"""
defaultdict(list,
            {6: ['banana'],
             10: ['strawberry', 'watermelon', 'grapefruit'],
             5: ['mango'],
             9: ['pineapple', 'blueberry'],
             4: ['kiwi']})
"""

만약에 collections.defaultdict 클래스 없이 위 코드를 작성해야했다면 다음과 같이 다소 지저분하게 작성했었을 것입니다.

def group_words(words):
    grouper = {}
    for word in words:
        length = len(word)
        if length not in grouper:
            grouper[length] = []
        grouper[length].append(word)
    return grouper

[보너스] 사전 기본값으로 빈 세트 설정

한 번 응용을 해볼까요? 위에서 작성한 코드에서 단어들을 길이에 따라 분류할 때 중복되지 않은 단어만 필요하다면 어떻게 해야 할까요? defaultdict 생성자에 list 함수 대신에 set 함수를 넘기고, append 함수 대신에 add 함수를 이용해서 단어를 넘기면 됩니다. :)

from collections import defaultdict

def group_words(words):
    grouper = defaultdict(set)
    for word in words:
        length = len(word)
        grouper[length].add(word)
    return grouper

전체 코드

본 포스팅에서 제가 작성한 전체 코드는 아래에서 직접 확인하고 실행해보실 수 있습니다.

https://dales.link/1bx

마치면서

지금까지 사전에서 제공하는 setdefault() 함수와 get() 함수, 그리고 collections 모듈에서 제공하는 defaultdict 클래스 이용해서 파이썬에서 사전을 다룰 때 기본값을 설정하는 방법에 대해서 알아보았습니다. 본 포스팅에서 소개한 요령들을 잘 활용해셔서 사전을 사용하실 때 좀 더 읽기쉬운 코드를 작성하실 수 있으셨으면 좋겠습니다.