이번 시간에는 파이썬의 collections 안에 있는 defaultdict에 대해 간단하게 알아보겠습니다. 언제 쓰면 좋은지 알아볼게요.

 


 딕셔너리나 set을 사용하다 보면 아래와 같은 에러를 자주 보신 적이 있을 겁니다.

 

 키가 없을 때 KeyError 에러를 뱉습니다. 혹은 하나의 key가 여러 개의 value를 가지고 있다면 어떤가요? 이렇게 하시는 경우가 많을 겁니다.

 

 

 먼저, 키 1이 있는지 검사합니다. 있으면 가져오고, 없으면 빈 리스트를 가져옵니다. 다음에, 가져온 리스트에 2를 추가합니다. 다음에 dic[1] = lt로 키 1의 값에 2를 append한 리스트를 대입합니다.

 

 

 결과는 위와 같습니다. 얼핏 보면, dic[1].append(2)만 있으면 될 거 같습니다. key 값이 있을 때에는 별 문제가 되지 않습니다. 그런데, 없을 때 문제가 됩니다. get으로 없을 때 리스트를 가져올 수 있긴 합니다. 그런데, 이것은 딕셔너리에 없습니다. 그래서 생각보다 복잡한 과정을 통해, 한 키에 여러 개의 value가 mapping 되는 경우를 처리하고 있습니다.

 


이러한 고민을, collections의 defaultdict로 해결할 수 있습니다. 클래스만 간단하게 보겠습니다.

 

 먼저, __init__ 함수입니다. 다른 것은 볼 필요가 없고, args[0]을 default_factory로 받고 있다는 것만 보면 됩니다. 중요한 것은 400번째 줄에 있는 __missing__ 입니다. 이 함수는 키가 없을 때 어떻게 처리하는지를 나타냅니다.

 

 

 default_factory가 None일 때에는 keyError를 돌려줍니다. 그렇지 않은 경우, 409번째 줄을 수행합니다. 이 409번째 줄이 무엇을 하는 것인가? key가 없는 경우, default_factory의 결과값을 value에 넣고, key에 맵핑시킵니다. 예를 들어, default_factory에 dict를 넘겨주면 아래와 같은 일이 수행됩니다.

 

 키가 없다면, 빈 딕셔너리를 만들고, 이 딕셔너리와 키 k를 맵핑시킨다. 이제 예제를 보겠습니다.

 

 

 3번째 줄부터 5번째 줄 까지, defaultdict의 dict에 무언가를 추가합니다. 그렇게 해도 될까요?

 

 

 놀랍게도 그게 됩니다. 일단 di는 defaultdict였습니다. 3번째 줄이 수행될 때 어떤 일이 일어날까요? di는 아무 것도 없는 빈 자료구조였습니다. 그러므로 1이 없었겠죠. 제가 default_factory를 dict로 세팅했기 때문에 아래와 같은 일이 수행됩니다.

 

 

 키 1이 없었기 때문에, factory에 의해 빈 딕셔너리가 리턴됩니다. 그러면 di[1]은 빈 딕셔너리가 리턴되는데요.

 

  di[1][1] = 2에 의해, di[1]의 value인 빈 딕셔너리에 키 1, 값 2인 쌍이 넣어집니다.

 

 

 다음에, di[1][3]에 의해, di[1]에 대응되는 dictionary에 키 3, 값 2인 쌍이 넣어집니다. 한 키에 대해 multiple value를 가지는 경우 처리하는 것은 어렵지 않네요. 조심해야 할 점이 하나 있습니다.

 

 수행 결과가 어떻게 나올까요? 둘 다 False가 나올 것만 같습니다.

 

 그런데, di[3]이 수행된 후, True가 나옵니다. 이는, __missing__ 이벤트가 발생했을 때 파이썬의 defaultdict는 키에 대응되는 값을 추가했기 때문입니다. 조심해야 할 부분입니다. 이제, 조금 더 응용된 예를 생각해 봅시다.

 

 


 파일은 소유자, 소유 그룹 등이 있어요. 소유자와 소유 그룹에 속한 유저, 둘 다 속하지 않는 유저에 주어지는 권한이 같지 않다는 것은 아실 수 있을 거에요. 우리는 특정 그룹에 어떠한 유저가 속하는지 빠르게 판단하려고 합니다. 어떻게 하면 좋을까요? 유저는 최대 수천개의 그룹에 속할 수 있다고 합시다.

 

 

 대략 이런 그림으로 판단하면 됩니다. 왜? 그룹에 대한 정보를 빠르게 찾아야 하기 때문입니다. 키 값을 빠르게 찾는 구조는? 해시 계열의 자료 구조일 겁니다. 여기서 'USER_NAME'이 group_dic['GROUP_NAME']에 있는지를 빠르게 판단하려면, in 절에 걸려있는 것 또한 set이나 dict 같이 키 값을 빠르게 찾을 수 있는 구조여야 합니다.

 

 그러면 group_dic은 dictionary 계열인데, value 값으로도 dictionary를 저장하는 구조임을 알 수 있습니다. 그리고 한 그룹에 대해 여러 유저가 속할 수 있으므로, 한 키에 대해 multi value를 가집니다. 따라서, group_dic은 defaultdic으로 처리하면 좋을 겁니다.

 

 

 위 프로그램은 제가 말한 것을 그대로 코드로 옮겨놓은 것입니다. group_dic의 key 값은 그룹 이름입니다. value는 그룹에 속한 유저들을 저장해 놓은 dictionary입니다. 3 ~ 5번째 줄을 수행하고 나면, 그룹 "CHO"에 속한 유저는 "A", "B"이고, 그룹 "GA"에 속한 유저는 "C"라는 정보가 저장됩니다.

 

 이제, "B"가 "CHO"라는 그룹에 속하는지 알아내기 위해 6번째 줄을 수행합니다. 다음에 "C"가 그룹 "CHO"에 속하는지 알아내기 위해 7번째 줄을 수행합니다. 수행 결과는 아래와 같습니다.

 

 

 의도한 대로 잘 나왔음을 알 수 있습니다.