본문 바로가기
  • hazard_dev@__
  • hazard_dev@__
Python

[Python] DefaultDict 다루기!!!

by Hazard3_o00sung 2020. 12. 10.
728x90

강력한 스크립트 언어, python 입니다

Powerful Structure of dict -> DefaultDict

파이썬에서는 그냥 dictonary 구조에서 내장 구조 dict도 있지만, 내장 라이브러리인 collections내부에 정의되어 있는 또 다른 딕셔너리 구조가 있는데, 그 친구의 이름이 바로 defaultdict입니다!! 파이썬의 딕셔너리는 다른 랭귀지에서의 맵(Map())과 비슷한 기능을 하죠, 그러니까 임의의 키값에 대응되는 값이 존재하는 게 맵이자, 파이썬에서는 딕셔너리입니다. 여하튼 그렇다 가정하게 되면, 각 값에 대응하는 값이 필요하지만, 프로그램에서 오류가 KeyError 등 심각한 오류가 발생했을 때 내장 딕셔너리는 커버할 수가 없게 됩니다. 그 이유로는 키값에 대응되는 값이 존재하지 않는데, 사용자 레벨에서 존재하지 않는 인덱스에 액세스 시도가 있었기 때문입니다... 물론 말도 안 되는 거긴 해도 프로그래머 입장에서는 꽤나 골치 아픈 문제임과 동시에 막아줘야 할 문제입니다. 하지만!!! Defaultdict는 이러한 문제점을 해결합니다.

 

아래 그림을 보겠습니다!

 

딕셔너리 자료구조_ designed by own

 

위 구조를 보게되면 키값의 a, b는 사상되는 값이 있으나, c에는 없습니다. 그렇다면 당연히 파이썬에서는 KeyError가 발생해야 할 수밖에 없을 것이고, 결과적으론 프로그래머가 잘못 작성한 프로그램이 되는 건가요? 물론 파이썬의 원칙 중 하나인 EAFP(Easier to Ask for Forgiveness than permission) 이 있긴 하나,,, 모든 걸 그런 방법으로 커버할 순 없죠, 좋은 방법도 아니고요 예를 들어서 이런 식으로 파이써닉하게 작성할 수 있다는 말인데,

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class foo:
    def __init__(self-> None:
        super().__init__()
 
    def eafp(self, foo):
        result = []
        for i,j in foo.items():
            result.append(i)
        if foo['d']:
            print('hello d')
        
 
if __name__ == "__main__":
    f = foo()
    bar = {"a" : 1"b" : 2"c" : 3}
    f.eafp(bar)
 
RESULT > 
    if foo['d']:
KeyError: 'd'
cs

 

위 코드에 결괏값을 보게 되면, KeyError가 발생했습니다. 쉽게 말해서 없는 키값에 엑세스 하려고 하지 마라는 말이겠죠? 근데 만약, 아래와 같은 방법대로 코드를 작성할 수도 있습니다. (하드 코딩된 점은 죄송합니다ㅠㅠㅠ 설명드리려면 코드가 더러워져서 이해 좀 부탁드려요)

 

1
2
3
4
5
6
7
8
9
def eafp(self, foo):
        result = []
        for i,j in foo.items():
            result.append(i)
        try:    
            if foo['d']:
                print('hello d')
        except KeyError as e:
            print(e)
cs

 

이렇게 작성되면 당연히 'd'이렇게 출력됩니다. 그러니까 에러의 원인이 출력되는 거죠. 이렇게 코드를 작성했을 때 문제점은 그냥 사실상 에러가 존재하지만 스킵하겠다는 거고, 가벼운 프로그램이나 나혼자 사용하는 프로그램에서야 문제없지만, 클라이언트의 피시에서는 심각한 오류를 발생할 수 있다는 점을 간과해선 안됩니다. 그렇기 때문에 차라리 defaultdict도 알아두시는 게 좋죠!

여기서 잠깐!!! 제 의견은 dict가 defaultdict보다 낫다 이런거 아닙니다! 고정된 인덱스에서의 접근은 dict를 사용해도 되고, 상황에 맞게 판단을 잘해야 한다는 거죠!🤣

And so on DefaultDict

말 그대로 default(기본)딕셔너리 구조다 이건대, 얘를 사용하는 이유는 대부분 눈치채셨겠지만, 초기값을 제공하는 딕셔너리다 이 말입니다. 그러니까 없는 키 값에 액세스 하더라도 오류가 발생하는 것이 아닌, 미리 정해진 값이 반환되는 그런 딕셔너리죠😇

 

그럼 사용법을 알아야하니까 한번 아래 코드를 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from collections import defaultdict
 
class foo:
    def __init__(self-> None:
        super().__init__()
 
    def printDict(self, foo):
        for i,j in foo.items():
            print(i,j)
        print(foo['c'])
        
def defaultValue():
    return 'wink'
 
if __name__ == "__main__":
    f = foo()
    dd = defaultdict(defaultValue)
    dd['a'= 1
    dd['b'= 2
    f.printDict(dd)
 
RESULT >
1
2
wink
cs

 

우선은 무조건 저 따라 실습은 해주세요!(필수) 클래스는 굳이 선언할 필요 없어요, 그냥 함수로만 작성해도 됩니다. 그리고 기본값을 되돌려주는 함수 defaultValue()를 선언해주고 wink 문자열을 반환하도록 합니다. 아래에는 defaultdict가 선언돼있는 걸 확인할 수 있는데, 생성 후에 값을 넣어주셔야 합니다. 그렇기 때문에 생성 후 키값에 대응되는 값을 넣어주었고요, 여기서 감이 좋으신 분들이라면 리스트 두 개를 생성해서 반복하면서 넣어줘도 되겠다 라는 생각을 하겠군요? 아 이미 하셨나요 ㅎㅎㅎㅎㅎㅎ

 

그렇게 했을 때, 우리는 'c'에 대응하는 값을 만들어주지 않았지만, wink라는 값이 나오면서 프로그램이 정상작동한 것을 볼 수 있습니다. 아주 유용한 친구니까, 그래도 뭐 적재적소에 맞게 dict와 defaultdict를 나눠서 써주시는 게 제일 베스트인 건 아시죠?

 

제가 말한 리스트대로 작성한다면 아래와 같습니다. 모두 하실줄 아시겠지만, 그래도 답답해서 보길 원하시는 분들이 계실 테니,,

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import defaultdict
 
 
if __name__ == "__main__":
    dd1 = defaultdict()
    foo = [1,2,3,4,5]
    bar = ['a','b','c','d','e']
 
    for i in range(len(foo)):
        dd1[foo[i]] = bar[i]
    print(dd1)
 
RESULT > 
defaultdict(None, {1'a'2'b'3'c'4'd'5'e'})
cs

 

이렇게 작성될 수 있습니다. 당연히 defaultdict를 생성할 때, 굳이 기본값을 제공하지 않아도 되지만, 그렇게 사용하면 defaultdict를 굳이 사용할 필요는 없게 되죠, 여하튼 dict와 defaultdict의 차이점을 이 정도만 이해하셔도 어느 정도 이해하셨으리라 생각합니다. 어려운 개념들은 아니기도 하고요!

 

감사합니다. 

 

잘 보셨다면, 댓글로 피드백, 문의 질문, 뭐든 환영합니다😍

728x90

댓글