간단하게 생각해서, 그냥 dictionary 보다는 defaultdict 를 사용하는 편이 여러모로 편하다.
RealPython 을 참고하여, 몇가지만 정리하려한다.
defaultdict 는, 키가 없는 dict 를 참조했을 때 KeyError 가 발생함을 방지하려는 목적이 기본이다.
예를 들어, 다음과 같이 기본 dict 로 작업하면 오류가 발생한다.
In [51]: aa = dict() In [52]: aa Out[52]: {} In [53]: aa['name'] = '몰라' In [54]: aa Out[54]: {'name': '몰라'} In [55]: aa['addr'] --------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-55-ebcb56d4c177> in <module> ----> 1 aa['addr'] KeyError: 'addr'
aa['name'] = '몰라'
처럼 키와 값을 모두 할당하면 문제가 없지만, aa['addr']
처럼 존재하지 않는 키를 참조했을 경우엔 KeyError 를 맞이해야만 한다.
이걸 방지하고자 defaultdict 를 사용한다.
from collections import defaultdict In [56]: cc = defaultdict(list) In [57]: cc['phone'] Out[57]: []
cc = defaultdict(list)
에서 처럼, dict 의 기본값을 list 로 줬기 때문에 cc['phone']
의 값은 빈리스트로 생성되었다. 일반 dict 라면 당연히 KeyError 가 나야한다.
그런데, 여기서 한가지 궁금증. defaultdict 의 첫번째 인수는 꼭 list, set, str 등이어야만 하나?
물론, 그렇지 않다. Callable 이면 뭐든 가능하다. 즉, 함수도 되고, 클래스도 된다.
따라서 이런 함수를 만들어 사용할 수도 있다. (Effective Python 2nd, p152 참고)
def log_missing2(): print('Key added') return list() dd = defaultdict(log_missing2) dd['street'] Key added [] dd defaultdict(<function __main__.log_missing2()>, {'street': []})
기본은 이렇고, 실제로는 저렇게 함수를 사용하는 방법보다는, Class 를 정의하고, __call__
메소드를 구현하는 방식이 좀 더 보기쉽고, 고급 사용법이라고 할 수 있겠다. (Effective Python 2nd, p155)
class BetterCountMissing: def __init__(self): self.added = 0 def __call__(self): self.added += 1 return 0 counter = BetterCountMissing() result = defaultdict(counter)
이 __call__ 메소드는 어떻게 실행이 되나? 그냥 간단하게 뒤에 괄호를 붙여주면 실행할 수 있다.
counter() 0
마지막으로 한가지만 더.
aa = defaultdict(list)
식으로, defaultdict 안에 넣을 때는 list
라고 했지만, 함수를 정의했을 때는 return list()
식으로 괄호를 붙였다.
왜 그랬을까?
defaultdict(list) 는, Callable 객체를 직접 호출하는 형식이므로 괄호를 넣으면 안된다. 반면, 함수에서 return 할 때는, 실제 객체를 생성해서 넘겨야 하므로 괄호를 붙여서 결과값을 만들어줘야 한다.
….
애매한 점이 조금은 해결되었으려나..
아울러, 오래도록 기억할 수 있으려나?