day 4 딕셔너리 사용하기

7 minute read

딕셔너리 응용하기

이번에는 딕셔너리의 키-값 쌍을 조작하는 메서드와 정보를 조회하는 메서드를 사용해보겠습니다. 그리고 for 반복문을 사용하여 키와 값에 접근하는 방법, 딕셔너리 표현식, 중첩 딕셔너리도 함께 대해 알아보겠습니다.

딕셔너리에 키-값

딕셔너리의 중요한 기능 중 하나가 바로 키-값 쌍 추가입니다. 다음과 같이 딕셔너리에 키-값 쌍을 추가하는 메서드는 2가지가 있습니다.

  • setdefault: 키-값 쌍 추가
  • update: 키의 값 수정, 키가 없으면 키-값 쌍 추가
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.setdefault('e')
>>> x
{'a': 10, 'b': 20, 'c': 30, 'd': 40, 'e': None}

>>> x.setdefault('f', 100)
100
>>> x
{'a': 10, 'b': 20, 'c': 30, 'd': 40, 'e': None, 'f': 100}

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.update(a=90)
>>> x
{'a': 90, 'b': 20, 'c': 30, 'd': 40}

>>> x.update(e=50)
>>> x
{'a': 90, 'b': 20, 'c': 30, 'd': 40, 'e': 50}

>>> x.update(a=900, f=60)
>>> x
{'a': 900, 'b': 20, 'c': 30, 'd': 40, 'e': 50, 'f': 60}

>>> y = {1: 'one', 2: 'two'}
>>> y.update({1: 'ONE', 3: 'THREE'})
>>> y
{1: 'ONE', 2: 'two', 3: 'THREE'}

>>> y.update([[2, 'TWO'], [4, 'FOUR']])
>>> y
{1: 'ONE', 2: 'TWO', 3: 'THREE', 4: 'FOUR'}

>>> y.update(zip([1, 2], ['one', 'two']))
>>> y
{1: 'one', 2: 'two', 3: 'THREE', 4: 'FOUR'}

참고 | setdefault와 update의 차이 setdefault는 키-값 쌍 추가만 할 수 있고, 이미 들어있는 키의 값은 수정할 수 없습니다. 하지만 update는 키-값 쌍 추가와 값 수정이 모두 가능합니다. 다음과 같이 setdefault로 이미 들어있는 키 ‘a’를 90으로 저장해도 ‘a’의 값은 바뀌지 않습니다.

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.setdefault('a', 90)
10
>>> x
{'a': 10, 'b': 20, 'c': 30, 'd': 40}

pop(키)는 딕셔너리에서 특정 키-값 쌍을 삭제한 뒤 삭제한 값을 반환합니다. 다음은 딕셔너리 x에서 키 ‘a’를 삭제한 뒤 10을 반환합니다.

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.pop('a')
10
>>> x
{'b': 20, 'c': 30, 'd': 40}

>>> x.pop('z', 0)
0

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> del x['a']
>>> x
{'b': 20, 'c': 30, 'd': 40}

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.popitem()
('d', 40)
>>> x
{'a': 10, 'b': 20, 'c': 30}

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.popitem()    # 파이썬 3.5 이하에서는 매번 삭제하는 키-값 쌍이 달라짐
('a', 10)
>>> x
{'b': 20, 'c': 30, 'd': 40}

clear()는 딕셔너리의 모든 키-값 쌍을 삭제합니다. 다음은 딕셔너리 x의 모든 키-값 쌍을 삭제하여 빈 딕셔너리 {}가 됩니다.

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.clear()
>>> x
{}

get(키)는 딕셔너리에서 특정 키의 값을 가져옵니다. 다음은 딕셔너리 x에서 키 ‘a’의 값을 가져옵니다.

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.get('a')
10

>>> x.get('z', 0)
0

딕셔너리에서 키-값 쌍을 모두 가져오기

  • items: 키-값 쌍을 모두 가져옴
  • keys: 키를 모두 가져옴
  • values: 값을 모두 가져옴
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.items()
dict_items([('a', 10), ('b', 20), ('c', 30), ('d', 40)])

>>> x.keys()
dict_keys(['a', 'b', 'c', 'd'])

>>> x.values()
dict_values([10, 20, 30, 40])

리스트와 튜플로 딕셔너리 만들기

>>> keys = ['a', 'b', 'c', 'd']
>>> x = dict.fromkeys(keys)
>>> x
{'a': None, 'b': None, 'c': None, 'd': None}

>>> y = dict.fromkeys(keys, 100)
>>> y
{'a': 100, 'b': 100, 'c': 100, 'd': 100}

참고 | defaultdict 사용하기

지금까지 사용한 딕셔너리(dict)는 없는 키에 접근했을 경우 에러가 발생합니다.

>>> x = {'a': 0, 'b': 0, 'c': 0, 'd': 0}
>>> x['z']    # 키 'z'는 없음
Traceback (most recent call last):
  File "<pyshell#5>", line 1, in <module>
    x['z']
KeyError: 'z'

그러면 에러가 발생하지 않게 하려면 어떻게 해야 할까요? 이때는 defaultdict를 사용합니다.

defaultdict는 없는 키에 접근하더라도 에러가 발생하지 않으며 기본값을 반환합니다. defaultdict는 collections 모듈에 들어있으며 기본값 생성 함수를 넣습니다.

defaultdict(기본값생성함수)

다음은 기본값이 0인 defaultdict 딕셔너리를 만듭니다.

>>> from collections import defaultdict    # collections 모듈에서 defaultdict를 가져옴
>>> y = defaultdict(int)    # int로 기본값 생성

딕셔너리 y에는 키 ‘z’가 없지만 y[‘z’]와 같이 키의 값을 가져와보면 0이 나옵니다. 왜냐하면 기본값을 0으로 설정했기 때문입니다.

>>> y['z']
0

defaultdict(int)처럼 int를 넣었는데 기본값이 왜 0인지 의문이 생길 수도 있습니다. int는 실수나 문자열을 정수로 변환하지만, 다음과 같이 int에 아무것도 넣지 않고 호출하면 0을 반환합니다.

>>> int()
0

defaultdict에는 특정 값을 반환하는 함수를 넣어주면 되는데, defaultdict(int)는 기본값 생성 함수로 int를 지정하여 0이 나오도록 만든 것입니다.

0이 아닌 다른 값을 기본값으로 설정하고 싶다면 다음과 같이 기본값 생성 함수를 만들어서 넣어주면 됩니다.

>>> z = defaultdict(lambda: 'python')
>>> z['a']
'python'
>>> z[0]
'python'

여기서는 문자열 ‘python’을 반환하는 lambda: ‘python’을 넣어서 ‘python’이 기본값이 되도록 설정했습니다. lambda는 ‘Unit 32 람다 표현식 사용하기’에서 자세히 설명하겠습니다.

반복문으로 모두 출력하기

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> for i in x:
...     print(i, end=' ')
...
a b c d

for 키, 값 in 딕셔너리.items():
     반복할 코드

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> for key, value in x.items():
...     print(key, value)
...
a 10
b 20
c 30
d 40

for key, value in {'a': 10, 'b': 20, 'c': 30, 'd': 40}.items():
    print(key, value)

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> for key in x.keys():
...     print(key, end=' ')
...
a b c d

>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> for value in x.values():
...     print(value, end=' ')
...
10 20 30 40

>>> keys = ['a', 'b', 'c', 'd']
>>> x = {key: value for key, value in dict.fromkeys(keys).items()}
>>> x
{'a': None, 'b': None, 'c': None, 'd': None}

x = {key: value for key, value in dict.fromkeys(keys).items()}

image

>>> {key: 0 for key in dict.fromkeys(['a', 'b', 'c', 'd']).keys()}               # 키만 가져옴
{'a': 0, 'b': 0, 'c': 0, 'd': 0}
>>> {value: 0 for value in {'a': 10, 'b': 20, 'c': 30, 'd': 40}.values()}        # 값을 키로 사용
{10: 0, 20: 0, 30: 0, 40: 0}

>>> {value: key for key, value in {'a': 10, 'b': 20, 'c': 30, 'd': 40}.items()}  # 키-값 자리를 바꿈
{10: 'a', 20: 'b', 30: 'c', 40: 'd'}

x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
 
for key, value in x.items():
    if value == 20:    # 값이 20이면
        del x[key]     # 키-값 쌍 삭제
 
print(x)

Traceback (most recent call last):
  File "C:\project\dict_del_by_value_error.py", line 3, in <module>
    for key, value in x.items():
RuntimeError: dictionary changed size during iteration 

별 문제 없이 잘 삭제될 것 같지만 반복 도중에 딕셔너리의 크기가 바뀌었다는 에러가 발생합니다. 즉, 딕셔너리는 for 반복문으로 반복하면서 키-값 쌍을 삭제하면 안 됩니다.

이때는 딕셔너리 표현식에서 if 조건문을 사용하여 삭제할 값을 제외하면 됩니다.

  • {키: 값 for 키, 값 in 딕셔너리 if 조건식}
  • dict({키: 값 for 키, 값 in 딕셔너리 if 조건식})
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x = {key: value for key, value in x.items() if value != 20}
>>> x
{'a': 10, 'c': 30, 'd': 40}

image

딕셔너리 안에서 딕셔너리


terrestrial_planet = {
    'Mercury': {
        'mean_radius': 2439.7,
        'mass': 3.3022E+23,
        'orbital_period': 87.969
    },
    'Venus': {
        'mean_radius': 6051.8,
        'mass': 4.8676E+24,
        'orbital_period': 224.70069,
    },
    'Earth': {
        'mean_radius': 6371.0,
        'mass': 5.97219E+24,
        'orbital_period': 365.25641,
    },
    'Mars': {
        'mean_radius': 3389.5,
        'mass': 6.4185E+23,
        'orbital_period': 686.9600,
    }
}
 
print(terrestrial_planet['Venus']['mean_radius'])  
6051.8
  • 딕셔너리[키][키]
  • 딕셔너리[키][키] = 값

딕셔너리의 할당과 복사

>>> x = {'a': 0, 'b': 0, 'c': 0, 'd': 0}
>>> y = x

y = x와 같이 딕셔너리를 다른 변수에 할당하면 딕셔너리는 두 개가 될 것 같지만 실제로는 딕셔너리가 한 개입니다.

x와 y를 is 연산자로 비교해보면 True가 나옵니다. 즉, 변수 이름만 다를 뿐 딕셔너리 x와 y는 같은 객체입니다.

>>> x is y
True

x와 y는 같으므로 y[‘a’] = 99와 같이 키 ‘a’의 값을 변경하면 딕셔너리 x와 y에 모두 반영됩니다.

>>> y['a'] = 99
>>> x
{'a': 99, 'b': 0, 'c': 0, 'd': 0}
>>> y
{'a': 99, 'b': 0, 'c': 0, 'd': 0}

딕셔너리 x와 y를 완전히 두 개로 만들려면 copy 메서드로 모든 키-값 쌍을 복사해야 합니다.

>>> x = {'a': 0, 'b': 0, 'c': 0, 'd': 0}
>>> y = x.copy()
>>> x is y
False
>>> x == y
True
>>> y['a'] = 99
>>> x
{'a': 0, 'b': 0, 'c': 0, 'd': 0}
>>> y
{'a': 99, 'b': 0, 'c': 0, 'd': 0}

중첩 딕셔너리의 할당과 복사

이제 y[‘a’][‘python’] = ‘2.7.15’와 같이 y의 값을 변경해보면 x와 y에 모두 반영됩니다.

>>> x = {'a': {'python': '2.7'}, 'b': {'python': '3.6'}}
>>> y = x.copy()
>>> y['a']['python'] = '2.7.15'
>>> x
{'a': {'python': '2.7.15'}, 'b': {'python': '3.6'}}
>>> y
{'a': {'python': '2.7.15'}, 'b': {'python': '3.6'}}

중첩 딕셔너리를 완전히 복사하려면 copy 메서드 대신 copy 모듈의 deepcopy 함수를 사용해야 합니다.

>>> x = {'a': {'python': '2.7'}, 'b': {'python': '3.6'}}
>>> import copy             # copy 모듈을 가져옴
>>> y = copy.deepcopy(x)    # copy.deepcopy 함수를 사용하여 깊은 복사
>>> y['a']['python'] = '2.7.15'
>>> x
{'a': {'python': '2.7'}, 'b': {'python': '3.6'}}
>>> y
{'a': {'python': '2.7.15'}, 'b': {'python': '3.6'}}

이제 딕셔너리 y의 값을 변경해도 딕셔너리 x에는 영향을 미치지 않습니다. copy.deepcopy 함수는 중첩된 딕셔너리에 들어있는 모든 딕셔너리를 복사하는 깊은 복사(deep copy)를 해줍니다.

Leave a comment