[Python] dict & defaultdict, OrderedDict

2 분 소요

키가 존재할 때와 존재하지 않을 때

>>> d = {'red':1, 'white':2}
>>> d['red'] = 2
>>> d
{'red':2, 'white':2}
>>> d['blue'] = 3
>>> d
{'red':2, 'white':2, 'blue':3}
>>> d['white'] += 1
>>> d
{'red':2, 'white':3, 'blue':3}
>>> d['green'] += 1
Trackback (most recent call last):
    File "<pyshell#45>", line n, in <module>
        d['green'] += 1
KeyError: 'green'

딕셔너리에 해당 키가 존재하는 경우 대입 연산의 결과가 값의 수정으로 이어지지만 존재하지 않은 경우 대입 연산의 결과가 새로운 키와 값의 추가로 이어진다.

하지만 += 연산의 경우 존재하지 않는 키에 대해서는 예외가 발생한다.

>>> s = 'robbot'
>>> d = {}
>>> for k in s:
        if k in d:
            d[k] += 1
        else:
            d[k] = 1

>>> d
{'r' : 1, 'o' : 2, 'b' : 2, 't' : 1}

위 코드처럼 예외를 처리해줄 수 있다.

setdefault 메소드

>>> s = 'robbot'
>>> d = {}
>>> for k in s:
        d[k] = d.setdefault(k, 0) + 1

>>> d
{'r' : 1, 'o' : 2, 'b' : 2, 't' : 1}

setdefault 메소드를 사용하면 if-else를 통한 예외릘 간단히 구현할 수 있다. k에 해당하는 키가 있을 때, 그 키의 값을 반환하고, 없다면 딕셔너리에 k:v 저장하고 v를 반환한다.



defaultdict

>>> from collections import defaultdict
>>> s = 'robbot'
>>> d = defaultdict(int)
>>> for k in s:
        d[k] += 1

>>> d
defaultdict(<class 'int'>, {'r':1, 'o':2, 'b':2, 't':1})
>>> print(d['r'], d['o'], d['b'], d['t'], sep=', ')
1, 2, 2, 1

또 다른 방법으로 디폴트 값을 갖는 딕셔너리를 생성하는 방법이다. 이 딕셔너리는 찾는 키가 없으면 예외를 발생시키지 않고 해당 키를 추가회되, 미리 등록해 놓은 함수가 반환하는 디폴트 값을 그 키의 값으로 저장한다.

위 코드에서는 int 함수를 등록했다.

>>> n = int('36')
>>> n
36
>>> n = int()
>>> n
0

int 함수는 문자열을 정수로 변환해 반환하는 함수이다. 다만 아무 값도 전달하지 않으면 0을 반환한다.

>>> def ret_zero():
        return 0

>>> from collections import defaultdict
>>> s = 'robbot'
>>> d = defaultdict(ret_zero)
>>> for k in s:
        d[k] += 1

>>> d
defaultdict(<class 'int'>, {'r':1, 'o':2, 'b':2, 't':1})
>>> print(d['r'], d['o'], d['b'], d['t'], sep=', ')
1, 2, 2, 1

다음과 같이 0을 반환하는 함수를 직접 만들어 defaultdict 함수에 전달해도 된다.

>>> from collections import defaultdict
>>> d = defaultdict(lambda: 7)
>>> d['z']
7
>>> d
defaultdict(<function <lambda> at 0x03626A98>, {'z' : 7})

람다식을 이용하는 방법도 있다.

OrderedDict

파이썬 3.7부터 딕셔너리가 저장순서를 유지한다.

>>> from collections import OrderedDict
>>> od = OrderedDict()
>>> od['a'] = 1
>>> od['b'] = 2
>>> od['c'] = 3

그럼에도 불구하고 OrderDict을 써야하는 상황이 존재한다.

>>> d1 = dict(a=1, b=2, c=3)
>>> d2 = dict(c=3, a=1, b=2)
>>> d1 == d2
True

d1, d2는 저장 순서는 다르고 내용물은 같다. 저장순서는 dict 객체를 비교함에 있어서 비교 대상이 아니다.

>>> from collections import OrderedDict
>>> od1 = OrderedDict(a=1, b=2, c=3)
>>> od2 = OrderedDict(c=3, a=1, b=2)
>>> od1 == od2

하지만 OrderedDict 객체는 비교에 있어서는 저장 순서도 중요하다. 아무리 내용물이 동일해도 저장 순서가 일치하지 않으면 == 연산결과 False이다.

>>> from collections import OrderedDict
>>> od = OrderedDict(a=1, b=2, c=3)
>>> for kv in od.items():
        print(kv, end= ' ')

('a', 1) ('b', 2) ('c', 3)
>>>
>>> od.move_to_end('b')
>>> for kv in od.items():
        print(kv, end=' ')

('a',1 ) ('c', 3) ('b', 2)
>>>
>>> od.move_to_end('b', last=False) # 매개변수 last에 False 전달하면 맨 앞으로 이동
>>> for kv in od.items():
        print(kv, end=' ')

('b', 2) ('a', 1) ('c', 3)

따라서 저장 순서 자체가 하나의 정보로써 의미를 갖는다면, 그리고 저장 순서를 바꿔야 할 가능성도 존재한다면 OrderedDict을 사용해야 한다.