딕셔너리 응용하기
- -
Unit 25. 딕셔너리 응용하기
'Unit 12 딕셔너리 사용하기'에서 딕셔너리의 기본적인 사용 방법을 배웠습니다. 이번에는 딕셔너리의 키-값 쌍을 조작하는 메서드와 정보를 조회하는 메서드를 사용해보겠습니다. 그리고 for 반복문을 사용하여 키와 값에 접근하는 방법, 딕셔너리 표현식, 중첩 딕셔너리도 함께 대해 알아보겠습니다.
25.1 딕셔너리 조작하기
그럼 딕셔너리를 조작하는 메서드와 정보를 얻는 메서드부터 알아보겠습니다. 파이썬에서 제공하는 딕셔너리 메서드는 여러 가지가 있지만 여기서는 자주 쓰는 메서드를 설명하겠습니다.
25.1.1 딕셔너리에 키-값 쌍 추가하기
딕셔너리의 중요한 기능 중 하나가 바로 키-값 쌍 추가입니다. 다음과 같이 딕셔너리에 키-값 쌍을 추가하는 메서드는 2가지가 있습니다.
- setdefault: 키-값 쌍 추가
- update: 키의 값 수정, 키가 없으면 키-값 쌍 추가
25.1.2 딕셔너리에 키와 기본값 저장하기4
setdefault(키)는 딕셔너리에 키-값 쌍을 추가합니다. setdefault에 키만 지정하면 값에 None을 저장합니다. 다음은 키 'e'를 추가하고 값에 None을 저장합니다.
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.setdefault('e')
>>> x
{'a': 10, 'b': 20, 'c': 30, 'd': 40, 'e': None}
setdefault(키, 기본값)처럼 키와 기본값을 지정하면 값에 기본값을 저장한 뒤 해당 값을 반환합니다. 다음은 키 'f'를 추가하고 값에 100을 저장한 뒤 100을 반환합니다.
>>> x.setdefault('f', 100)
100
>>> x
{'a': 10, 'b': 20, 'c': 30, 'd': 40, 'e': None, 'f': 100}
25.1.3 딕셔너리에서 키의 값 수정하기
키-값 쌍을 추가했으면 값을 수정하고 싶을 수도 있겠죠? 이때는 update 메서드를 사용합니다.
update(키=값)은 이름 그대로 딕셔너리에서 키의 값을 수정합니다. 예를 들어 딕셔너리가 x = {'a': 10}이라면 x.update(a=90)과 같이 키에서 작은따옴표 또는 큰따옴표를 빼고 키 이름과 값을 지정합니다.
그럼 키 'a'의 값을 90으로 수정해보겠습니다.
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.update(a=90)
>>> x
{'a': 90, 'b': 20, 'c': 30, 'd': 40}
만약 딕셔너리에 키가 없으면 키-값 쌍을 추가합니다. 딕셔너리 x에는 키 'e'가 없으므로 x.update(e=50)을 실행하면 'e': 50을 추가합니다.
>>> x.update(e=50)
>>> x
{'a': 90, 'b': 20, 'c': 30, 'd': 40, 'e': 50}
update는 키-값 쌍 여러 개를 콤마로 구분해서 넣어주면 값을 한꺼번에 수정할 수 있습니다. 이때도 키가 있으면 해당 키의 값을 수정하고 없으면 키-값 쌍을 추가합니다. 다음은 키 'a'의 값을 900으로 수정하고, 'f': 60을 추가합니다.
>>> x.update(a=900, f=60)
>>> x
{'a': 900, 'b': 20, 'c': 30, 'd': 40, 'e': 50, 'f': 60}
update(키=값)은 키가 문자열일 때만 사용할 수 있습니다. 만약 키가 숫자일 경우에는 update(딕셔너리)처럼 딕셔너리를 넣어서 값을 수정할 수 있습니다.
>>> y = {1: 'one', 2: 'two'}
>>> y.update({1: 'ONE', 3: 'THREE'})
>>> y
{1: 'ONE', 2: 'two', 3: 'THREE'}
다른 방법으로는 리스트와 튜플을 이용하는 방법도 있습니다. update(리스트), update(튜플)은 리스트와 튜플로 값을 수정합니다. 여기서 리스트는 [[키1, 값1], [키2, 값2]] 형식으로 키와 값을 리스트로 만들고 이 리스트를 다시 리스트 안에 넣어서 키-값 쌍을 나열해줍니다(튜플도 같은 형식).
>>> y.update([[2, 'TWO'], [4, 'FOUR']])
>>> y
{1: 'ONE', 2: 'TWO', 3: 'THREE', 4: 'FOUR'}
특히 update(반복가능한객체)는 키-값 쌍으로 된 반복 가능한 객체로 값을 수정합니다. 즉, 다음과 같이 키 리스트와 값 리스트를 묶은 zip 객체로 값을 수정할 수 있습니다.
>>> y.update(zip([1, 2], ['one', 'two']))
>>> y
{1: 'one', 2: 'two', 3: 'THREE', 4: 'FOUR'}
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}
25.1.4 딕셔너리에서 키-값 쌍 삭제하기
이번에는 딕셔너리에서 키-값 쌍을 삭제하는 메서드를 사용해보겠습니다.
pop(키)는 딕셔너리에서 특정 키-값 쌍을 삭제한 뒤 삭제한 값을 반환합니다. 다음은 딕셔너리 x에서 키 'a'를 삭제한 뒤 10을 반환합니다.
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.pop('a')
10
>>> x
{'b': 20, 'c': 30, 'd': 40}
pop(키, 기본값)처럼 기본값을 지정하면 딕셔너리에 키가 있을 때는 해당 키-값 쌍을 삭제한 뒤 삭제한 값을 반환하지만 키가 없을 때는 기본값만 반환합니다. 딕셔너리 x에는 키 'z'가 없으므로 기본값으로 지정한 0을 반환합니다.
>>> x.pop('z', 0)
0
pop 대신 del로 특정 키-값 쌍을 삭제할 수도 있습니다. 이때는 [ ]에 키를 지정하여 del을 사용합니다. 다음은 딕셔너리 x의 키 'a'를 삭제합니다.
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> del x['a']
>>> x
{'b': 20, 'c': 30, 'd': 40}
25.1.5 딕셔너리에서 임의의 키-값 쌍 삭제하기
popitem()은 딕셔너리에서 임의의 키-값 쌍을 삭제한 뒤 삭제한 키-값 쌍을 튜플로 반환합니다. 이 메서드는 파이썬 버전에 따라 동작이 달라지는데, 파이썬 3.6 이상에서는 마지막 키-값 쌍을 삭제하며 3.5 이하에서는 임의의 키-값 쌍을 삭제합니다.
이 책에서는 파이썬 3.6 이상을 기준으로 설명하겠습니다. 다음은 딕셔너리 x에서 마지막 키-값 쌍인 'd': 40을 삭제합니다.
파이썬 3.6
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.popitem()
('d', 40)
>>> x
{'a': 10, 'b': 20, 'c': 30}
참고로 파이썬 3.5와 그 이하 버전에서 popitem 메서드를 사용하면 임의의 키-값을 삭제하므로 매번 삭제하는 키-값 쌍이 달라집니다.
파이썬 3.5
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.popitem() # 파이썬 3.5 이하에서는 매번 삭제하는 키-값 쌍이 달라짐
('a', 10)
>>> x
{'b': 20, 'c': 30, 'd': 40}
25.1.6 딕셔너리의 모든 키-값 쌍을 삭제하기
clear()는 딕셔너리의 모든 키-값 쌍을 삭제합니다. 다음은 딕셔너리 x의 모든 키-값 쌍을 삭제하여 빈 딕셔너리 {}가 됩니다.
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.clear()
>>> x
{}
25.1.7 딕셔너리에서 키의 값을 가져오기
이번에는 딕셔너리에서 키의 값을 가져오거나 저장하는 메서드를 사용해보겠습니다.
get(키)는 딕셔너리에서 특정 키의 값을 가져옵니다. 다음은 딕셔너리 x에서 키 'a'의 값을 가져옵니다.
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.get('a')
10
get(키, 기본값)처럼 기본값을 지정하면 딕셔너리에 키가 있을 때는 해당 키의 값을 반환하지만 키가 없을 때는 기본값을 반환합니다. 딕셔너리 x에는 키 'z'가 없으므로 기본값으로 지정한 0을 반환합니다.
>>> x.get('z', 0)
0
25.1.8 딕셔너리에서 키-값 쌍을 모두 가져오기
딕셔너리는 키와 값을 가져오는 다양한 메서드를 제공합니다.
- items: 키-값 쌍을 모두 가져옴
- keys: 키를 모두 가져옴
- values: 값을 모두 가져옴
다음과 같이 items()는 딕셔너리의 키-값 쌍을 모두 가져옵니다.
>>> x = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
>>> x.items()
dict_items([('a', 10), ('b', 20), ('c', 30), ('d', 40)])
keys()는 키를 모두 가져옵니다.
>>> x.keys()
dict_keys(['a', 'b', 'c', 'd'])
values()는 값을 모두 가져옵니다.
>>> x.values()
dict_values([10, 20, 30, 40])
이 메서드들은 보통 for 반복문과 조합해서 사용하는데 자세한 내용은 뒤에서 설명하겠습니다.
25.1.9 리스트와 튜플로 딕셔너리 만들기
이번에는 리스트(튜플)로 딕셔너리를 만들어보겠습니다.
먼저 keys = ['a', 'b', 'c', 'd']처럼 키가 들어있는 리스트를 준비합니다(튜플도 됩니다). 그리고 dict.fromkeys에 키가 들어있는 리스트를 넣으면 딕셔너리를 생성합니다.
dict.fromkeys(키 리스트)는 키 리스트로 딕셔너리를 생성하며 값은 모두 None으로 저장합니다.
>>> keys = ['a', 'b', 'c', 'd']
>>> x = dict.fromkeys(keys)
>>> x
{'a': None, 'b': None, 'c': None, 'd': None}
dict.fromkeys(키 리스트, 값)처럼 키 리스트와 값을 지정하면 해당 값이 키의 값으로 저장됩니다.
>>> y = dict.fromkeys(keys, 100)
>>> y
{'a': 100, 'b': 100, 'c': 100, 'd': 100}
지금까지 사용한 딕셔너리(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 람다 표현식 사용하기'에서 자세히 설명하겠습니다.
리스트와 마찬가지로 딕셔너리도 for 반복문과 if 조건문을 사용하여 딕셔너리를 생성할 수 있습니다. 다음과 같이 딕셔너리 안에 키와 값, for 반복문을 지정하면 됩니다.
- {키: 값 for 키, 값 in 딕셔너리}
- dict({키: 값 for 키, 값 in 딕셔너리})
>>> 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}
딕셔너리 표현식을 사용할 때는 for in 다음에 딕셔너리를 지정하고 items를 사용합니다. 그리고 키, 값을 가져온 뒤에는 키: 값 형식으로 변수나 값을 배치하여 딕셔너리를 생성하면 됩니다.
x = {key: value for key, value in dict.fromkeys(keys).items()}
즉, dict.fromkeys(keys).items()로 키-값 쌍을 구한 뒤 키는 변수 key, 값은 변수 value에 꺼내고 최종적으로 key와 value를 이용하여 딕셔너리를 만듭니다.
▼ 그림 25-1 딕셔너리 표현식의 동작 순서
물론 다음과 같이 keys로 키만 가져온 뒤 특정 값을 넣거나, values로 값을 가져온 뒤 값을 키로 사용할 수도 있습니다.
>>> {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'}
25.3.1 딕셔너리 표현식에서 if 조건문 사용하기
그런데 딕셔너리 표현식을 사용해보면 복잡하기만 하고 dict.fromkeys 함수만 사용한 결과와 큰 차이점이 없습니다. 대신 딕셔너리 표현식은 딕셔너리에서 특정 값을 찾아서 삭제할 때 유용합니다.
딕셔너리는 특정 키를 삭제하는 pop 메서드만 제공할 뿐 특정 값을 삭제하는 메서드는 제공하지 않습니다. 그러면 특정 값을 찾아서 키-값 쌍을 삭제하려면 어떻게 해야 할까요? 간단하게 for 반복문으로 반복하면서 del로 삭제하는 방식을 떠올릴 수 있습니다.
dict_del_by_value_error.py
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}
딕셔너리 표현식에서 if value != 20과 같이 if 조건문을 지정하면 값이 20이 아닌 키-값 쌍으로 다시 딕셔너리를 만듭니다. 직접 키-값 쌍을 삭제하는 방식이 아니라 삭제할 키-값 쌍을 제외하고 남은 키-값 쌍으로 딕셔너리를 새로 만드는 것이죠.
▼ 그림 25-2 딕셔너리 표현식에서 if 조건문 사용하기
이번에는 딕셔너리 안에서 딕셔너리를 사용하는 중첩 딕셔너리를 알아보겠습니다. 다음과 같이 딕셔너리는 값 부분에 다시 딕셔너리가 계속 들어갈 수 있습니다.
- 딕셔너리 = {키1: {키A: 값A}, 키2: {키B: 값B}}
예를 들어 지구형 행성의 반지름, 질량, 공전주기를 딕셔너리로 표현해보겠습니다.
dict_dict.py
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
실행 결과
6051.8
딕셔너리 terrestrial_planet에 키 'Mercury', 'Venus', 'Earth', 'Mars'가 들어있고, 이 키들은 다시 값 부분에 딕셔너리를 가지고 있습니다. 즉, 중첩 딕셔너리는 계층형 데이터를 저장할 때 유용합니다.
딕셔너리 안에 들어있는 딕셔너리에 접근하려면 딕셔너리 뒤에 [ ](대괄호)를 단계만큼 붙이고 키를 지정해주면 됩니다.
- 딕셔너리[키][키]
- 딕셔너리[키][키] = 값
여기서는 딕셔너리가 두 단계로 구성되어 있으므로 대괄호를 두 번 사용합니다. 그래서 금성(Venus)의 반지름(mean radius)를 출력하려면 다음과 같이 먼저 'Venus'를 찾아가고 다시 'mean_radius'의 값을 가져오면 됩니다.
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와 y를 is 연산자로 비교해보면 False가 나옵니다. 즉, 두 딕셔너리는 다른 객체입니다. 그러나 복사한 키-값 쌍은 같으므로 ==로 비교하면 True가 나옵니다.
>>> x is y
False
>>> x == y
True
딕셔너리 x와 y는 별개이므로 한쪽의 값을 변경해도 다른 딕셔너리에 영향을 미치지 않습니다. 다음과 같이 딕셔너리 y에서 키 'a'의 값을 변경하면 딕셔너리 x는 그대로이고 딕셔너리 y만 바뀝니다.
>>> y['a'] = 99
>>> x
{'a': 0, 'b': 0, 'c': 0, 'd': 0}
>>> y
{'a': 99, 'b': 0, 'c': 0, 'd': 0}
25.5.1 중첩 딕셔너리의 할당과 복사 알아보기
그럼 딕셔너리 안에 딕셔너리가 들어있는 중첩 딕셔너리도 copy 메서드로 복사하면 될까요? 다음과 같이 중첩 딕셔너리를 만든 뒤 copy 메서드로 복사합니다.
>>> x = {'a': {'python': '2.7'}, 'b': {'python': '3.6'}}
>>> y = x.copy()
이제 y['a']['python'] = '2.7.15'와 같이 y의 값을 변경해보면 x와 y에 모두 반영됩니다.
>>> 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)를 해줍니다.
지금까지 딕셔너리의 다양한 메서드와 응용 방법을 배웠는데, 내용이 다소 어려웠습니다. 딕셔너리의 메서드는 모두 외우지 않아도 되며 파이썬을 사용하다 보면 자연스럽게 익히게 됩니다. 여기서는 딕셔너리에 반복문을 사용하는 방법이 중요합니다. 다른 부분은 필요할 때 다시 돌아와서 찾아보세요.
'개인공부 > Python' 카테고리의 다른 글
세트 (0) | 2022.07.28 |
---|---|
python 심사문제 UNIT(25 ~ 35) (0) | 2022.07.28 |
리스트와 튜플 응용하기 (0) | 2022.07.28 |
python 심사문제 UNIT(22 ~ 24) (0) | 2022.07.27 |
python 심사문제 UNIT(13 ~ 21) (0) | 2022.07.26 |
소중한 공감 감사합니다