파이썬 사전의 기본값 처리 (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
이렇게 사전에 기본값을 설정해줘야 할 때는 파이썬의 내장 모듈인 collections
의 defaultdict
클래스를 사용하면 딱 인데요.
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
전체 코드
본 포스팅에서 제가 작성한 전체 코드는 아래에서 직접 확인하고 실행해보실 수 있습니다.
마치면서
지금까지 사전에서 제공하는 setdefault()
함수와 get()
함수, 그리고 collections
모듈에서 제공하는 defaultdict
클래스 이용해서 파이썬에서 사전을 다룰 때 기본값을 설정하는 방법에 대해서 알아보았습니다.
본 포스팅에서 소개한 요령들을 잘 활용해셔서 사전을 사용하실 때 좀 더 읽기쉬운 코드를 작성하실 수 있으셨으면 좋겠습니다.