몰입공간

[Python] defaultdict과 __missing__() 메서드 (Defaultdict and __missing__() method in builtin dict class) 본문

Programming/Python

[Python] defaultdict과 __missing__() 메서드 (Defaultdict and __missing__() method in builtin dict class)

sahayana 2022. 5. 15. 23:47


#1.  defaultdict

 

기본적으로 파이썬의 딕셔너리 자료형은 검색하려고 하는 key가 존재하지 않으면 KeyError를 내보냅니다.

이러한 KeyError를 핸들링하여 많은 일들을 할 수 있으면서도 반면에 KeyError를 raise하지 않으면서 자동으로
key를 생성 및 값을 설정할 수 있는 방법이 defaultdict__missing__() 메서드를 설정하는 것입니다.

 

defaultdict은 존재하지 않는 key로 검색할 때, 사용자가 정한 항목으로 생성하도록 설정하는 딕셔너리 자료형 입니다.

일반적인 dict 자료형으로 KeyError를 핸들링했을 때는 다음과 같습니다.

my_dict = {'a':list()} # Not using defaultdict
iterwords = 'abcd'

for word in iterwords:
    try:
        my_dict[word].append(word)
    except KeyError:
        my_dict.update({word:list(word)})

print(my_dict)
# {'a': ['a'], 'b': ['b'], 'c': ['c'], 'd': ['d']}

try 구문으로 KeyError가 발생하면 key=value 쌍을 새로 업데이트하는 코드입니다.

defaultdict을 활용하면 다음과 같이 간단하게 표현 가능합니다.

from collections import defaultdict	# collections 모듈에서 import
my_dict = defaultdict(list) # default로 list 지정

for word in iterwords:
    my_dict[word].append(word)  # Key가 없어도 default로 설정한 list 객체를 생성하고 매핑한다.

print(my_dict)
# defaultdict(<class 'list'>, {'a': ['a'], 'b': ['b'], 'c': ['c'], 'd': ['d']})

defaultdict의 파라미터로 생성할 항목을 지정합니다.

Key가 없는 경우, 에러를 발생시키지 않고 key=value (여기선 list 객체) 쌍을 새로 업데이트 합니다.

 


#2. __missing__() 메서드

 

__missing__() 메서드를 가지고 있는 사용자 정의 dict 클래스를 구현하여 KeyError를 발생시키지 않는 방법입니다.

__missing__()메서드는 기본 dict 클래스에는 정의되어 있지 않지만, dict 클래스를 상속한 커스터마이징 클래스를
생성하고  __missing__() 메서드를 정의하면 dict.__getitem__() 메서드가 key를 발견할 수 없을 때,

KeyError를 발생시키지 않고 __missing__() 메서드를 호출합니다.

 

class ListKeyDict(dict):    # defaultdict(list) 와 비슷한 커스텀 Dict 구현 by using __missing__() method
    def __missing__(self, key):
        self[key] = list()
        return self[key]

    def __contains__(self, key: object) -> bool:
        return key in self.keys() 

my_dict = ListKeyDict()
for word in iterwords:
    my_dict[word].append(word)
print('Dict 상속한 ListKeyDict:', my_dict)
# Dict 상속한 ListKeyDict: {'a': ['a'], 'b': ['b'], 'c': ['c'], 'd': ['d']}
print('a in my_dict:', 'a' in my_dict)
# True

위에서 생성한 defaultdict(list)와 동일한 기능을 하는 코드입니다.

 


#3. UserDict

 

dict의 경우 파이썬의 근본적인 bulitin 클래스이기 때문에 아무래도 dict 자체를 상속받아 메서드를 오버라이딩하기에 까다로운 부분이 있습니다. 따라서 파이썬에서는 커스터마이징 dict 클래스를 구현할 경우 UserDict을 상속받을 것을 친절하게 제시합니다.

 

from collections import UserDict

class ListKeyDict(UserDict):    # defaultdict(list) 와 비슷한 커스텀 Dict 구현 by using __missing__() method
    def __missing__(self, key):
        self[key] = list()
        return self[key]
    
    def __contains__(self, key: object) -> bool:
        return key in self.data # UserDict은 dict을 상속하지 않고 내부에 실제 항목을 담고 있는 data라고 하는 dict객체를 가지고 있다.

my_dict = ListKeyDict()
for word in iterwords:
    my_dict[word].append(word)
print('UserDict 상속한 ListKeyDict:', my_dict)
# UserDict 상속한 ListKeyDict: {'a': ['a'], 'b': ['b'], 'c': ['c'], 'd': ['d']}
print('a in my_dict:', 'a' in my_dict)
# True

기존 dict 클래스를 상속받았을 때와 같은 기능을 합니다. 

달라진 점은 UserDict의 경우 'data'라는 실제 데이터를 가진 dict 객체를 내부적으로 가지고 있습니다.

 


#4. 정리

 

  • 딕셔너리 자료형에서 setdefault(포스팅에서 다루지 않음), defaultdict 등을 통해 KeyError를 발생시키지 않고,
    로직을 업데이트 할 수 있다.
  • 혹은, __missing__() 메서드를 가진 사용자 정의 dict 클래스를 구현하는 것도 매우 효과적인 방법이다.
  • 사용자 정의 dict 클래스를 구현한다면 dict 클래스보다는 UserDict 클래스를 상속 받는 것이 좋다.

#5. 참고

 

  • 전문가를 위한 파이썬 (O'REILLY / 한빛미디어)

 

Comments